Direct naar de hoofdinhoud
Module 10 · Les 4 van 440/57 in de cursus~12 min
Modulelessen (4/4)

Het N+1-probleem

Bij backend-ontwikkeling is een van de meest voorkomende fouten die de API-prestaties catastrofaal ondermijnen het beruchte "N + 1 Queries"-probleem.

Het sluipt meestal onbekend binnen bij de ontwikkelaar die bibliotheekabstracties zoals "ORM's" (Object-Relational Mappers: bijvoorbeeld Prisma of Sequelize) gebruikt om vanuit Node.js met de database te praten.

Anatomie van de ramp

Stel je voor dat een app de volledige vermelding op de e-commercestartpagina wil retourneren: Alle producten samen met de reeks van de categorienaam waartoe elke product behoort. Gezien vanaf de Node.js-kant zou een onervaren programmeur de iteratieve lus schrijven:

  1. products = await db.query("SELECT * FROM products"); (Eén grote, omvangrijke operatie 1)
  2. De ontwikkelaar itereert (for..of) over elke product in de array van 100 gevonden
  3. Bij elke stap wachten ze in Node.js op een iteratieve subquery categoryName = await db.query("SELECT name FROM categories WHERE id = " + product.category_id); en integreren deze in het resultaat (N secundaire iteraties van langzame, kleine queries!)

Totaal aantal verzoeken aan de Postgres-daemon: 1 + 100 lokale iteraties = 101 micronetwerkrondreizen tussen Node.js en de DB. 101 retourvluchten veroorzaken verschrikkelijke netwerkoverhead en lelijke latentie. Op echte schaal bestaat N misschien niet uit 100, maar uit 10.000 rijen met totale facturen om te verkennen! De API keerde niet terug voordat een time-out van 5xx werd bereikt.

Het echte native antwoord: JOIN of aggregatie in één enkele zoekopdracht

Door meesterlijk gebruik te maken van met de hand geschreven DBMS-abstracties, in plaats van triviale ORM-opdrachten in Node, worden de uitvoeringen in milliseconden samengevoegd in een enkele, onverslaanbare query die de "N+1"-puinhoop oplost:

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

Totaal API/DB-round trips: 1 unieke query, gefuseerd, wiskundig geoptimaliseerd door de eigen indexen van Postgres, volledig afgehandeld met 0 milliseconden extra netwerklatentie.

Alternatief voor JSON-aggregatie

Met moderne Postgres-versies kun je zelfs de hele complexe join teruggeven, genest als JSON met json_agg, waardoor je JavaScript de array products kan ontvangen, inclusief hun subarray van opgemaakte recensies, zonder enige lokale scriptlijm! (In de geavanceerde paden en uitdagingen bestudeer je de JSONb-clausule van Postgres.)

Jouw beurt

Oefening#sql.m10.l4.e1
Pogingen: 0Laden…

Laten we een echt N+1-probleem oplossen dat in de maak is.\nNode zou een globale Select hebben uitgevoerd en vervolgens elke klant hebben doorlopen om hun naam te vinden als het een samenvatting wilde van de bestellingen die vanochtend zijn verzonden.\nLos het op door in één native opname een JOIN af te drukken tussen 'klanten' (gebruik de originele tekstuele alias 'c') en 'bestellingen' (alias 'o').\nExtraheer 2 triviale velden (waardoor formele aliasvoorvoegsels worden afgedwongen): 'c.first_name' (van de klant) en 'o.status' (van de lopende bestelling).

Editor laden…
Toon hint

Syntaxis: SELECT c.voornaam, o.status VAN klanten c JOIN bestellingen o AAN c.id = o.klant_id;

Oplossing beschikbaar na 3 pogingen

Onderzoek naar de efficiëntie van GROUP BY

Oefening#sql.m10.l4.e2
Pogingen: 0Laden…

Als er geen logische Group-Bys in de database worden gebruikt om het totale aantal per rij en categorie te verkrijgen, zou Node.js de database eindeloos aanroepen en opblazen in N+1.\nSchrijf een enkele query die het veld 'stad' selecteert uit 'klanten' en de standaard Postgres-telling 'COUNT(id)' met behulp van het terminal-trefwoord om ze uniek op naam te aggregeren.

Editor laden…
Toon hint

Een triviaal: SELECTEER stad, COUNT(id) VAN klanten GROEPEREN OP stad;

Oplossing beschikbaar na 3 pogingen