Passer au contenu principal
eLearner.app
Module 10 · Leçon 4 sur 440/57 dans le cours~12 min
Leçons du module (4/4)

Le problème N+1

Dans le développement back-end, l'une des erreurs les plus fréquentes qui nuisent de manière catastrophique aux performances de l'API est le fameux problème "N + 1 Requêtes".

Il s'introduit généralement à l'insu du développeur qui utilise des abstractions de bibliothèque telles que les « ORM » (Object-Relational Mappers : par exemple Prisma ou Sequelize) pour communiquer avec la base de données à partir de Node.js.

Anatomie de la catastrophe

Imaginez qu'une application souhaite renvoyer l'intégralité de la page d'accueil du commerce électronique : Tous les produits ainsi que la chaîne du nom de la catégorie à laquelle chacun appartient. Vu du côté de Node.js, un programmeur inexpérimenté écrirait la boucle itérative :

  1. products = await db.query("SELECT * FROM products"); (Une opération importante et volumineuse 1)
  2. Le développeur parcourt (for..of) sur chaque product dans le tableau de 100 trouvés
  3. À chaque étape, dans Node.js, ils attendent une sous-requête itérative categoryName = await db.query("SELECT name FROM categories WHERE id = " + product.category_id); et l'intègrent dans le résultat (N itérations secondaires de requêtes lentes et minuscules !)

Total des requêtes adressées au démon Postgres : 1 + 100 itérations locales = 101 allers-retours du micro-réseau entre Node.js et la base de données. 101 allers-retours entraînent une surcharge réseau épouvantable et une latence horrible. À échelle réelle, N ne représente peut-être pas 100 mais 10 000 lignes de factures totales à explorer ! L'API ne reviendrait pas avant d'atteindre un délai d'attente de 5xx.

La vraie réponse native : JOIN ou agrégation en une seule requête

L'utilisation magistrale d'abstractions de SGBD écrites à la main, au lieu de commandes ORM triviales dans Node, fusionne les exécutions en quelques millisecondes dans une requête unique et imbattable qui résout le désordre "N+1" :

SQL
SELECT
  p.id,
  p.name,
  c.name AS category_name
FROM products p
JOIN categories c ON p.category_id = c.id;

Total des allers-retours API/DB : 1 requête unique, fusionnée, optimisée mathématiquement par les propres index de Postgres, entièrement gérée avec 0 milliseconde de latence réseau supplémentaire.

Alternative à l'agrégation JSON

Les versions modernes de Postgres vous permettent même de renvoyer l'intégralité de la jointure complexe imbriquée en JSON avec json_agg, permettant à votre JavaScript de recevoir le tableau products, y compris son sous-tableau de critiques formatées sans aucune colle de script local ! (Dans les parcours et défis avancés, vous étudierez la clause JSONb de Postgres.)

A ton tour

Exercice#sql.m10.l4.e1
Tentatives : 0Chargement…

Résolvons un véritable problème N+1 en devenir. Node aurait exécuté une sélection globale, puis parcouru chaque client pour trouver leurs noms s'il voulait un récapitulatif des commandes expédiées ce matin. Résolvez-le en m'imprimant d'un seul coup natif un JOIN entre 'clients' (utilisez l'alias textuel d'origine 'c') et 'commandes' (alias 'o'). Extrayez 2 champs triviaux (forçant l'alias formel préfixe) : 'c.first_name' (du client) et 'o.status' (de la commande en attente).

Chargement de l'éditeur…
Afficher l'indice

Syntaxe : SELECT c.first_name, o.status FROM clients c JOIN commandes o ON c.id = o.customer_id ;

Solution disponible après 3 tentatives

Explorer l'efficacité de GROUP BY

Exercice#sql.m10.l4.e2
Tentatives : 0Chargement…

Si les Group-By logiques ne sont pas utilisés dans la base de données pour obtenir le nombre total par ligne et par catégorie, Node.js appellerait la base de données sans fin, explosant en N+1. Écrivez une seule requête qui sélectionne le champ « ville » parmi « clients » et le nombre Postgres standard « COUNT(id) » en utilisant le mot-clé terminal pour les agréger de manière unique par nom.

Chargement de l'éditeur…
Afficher l'indice

Un trivial : SELECT city, COUNT(id) FROM clients GROUP BY city ;

Solution disponible après 3 tentatives