eduardweb.
PerformanceIntermediar#nodejs#performance#prisma#postgresql#backend

N+1 queries în Prisma: Cum le prinzi cu $metrics și le rezolvi fără să pui memoria pe butuci

De Radu Grigore, 30 iul. 2026 · 10 vizualizări · 2 like-uri

Postat 30 iul. 2026
typescript
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

// 1. Detecează N+1 folosind $metrics
async function checkQueryCount() {
  const metrics = await prisma.$metrics.json();
  const queryCounter = metrics.counters.find(c => c.key === 'queries_total');
  console.log(`Total query-uri SQL executate: ${queryCounter?.value}`);
}

// ❌ GREȘIT: Generează N+1 query-uri (1 + N)
async function getBadData() {
  const users = await prisma.user.findMany({ take: 50 });
  return Promise.all(
    users.map(async (user) => {
      const posts = await prisma.post.findMany({ where: { authorId: user.id } });
      return { ...user, posts };
    })
  );
}

// ✅ CORECT: Single query cu select/include limitat
async function getOptimizedData() {
  return prisma.user.findMany({
    take: 50,
    select: {
      id: true,
      email: true,
      posts: {
        select: { id: true, title: true },
        take: 10 // Limitezi dimensiunea payload-ului în memorie
      }
    }
  });
}

Eram convins că Prisma rezolvă automat batching-ul până când am văzut un endpoint de dashboard care dura 4.2 secunde la doar 300 de utilizatori concurenți. Când am deschis logs-urile, Prisma executa aproape 800 de query-uri SQL pentru o singură pagină — clasicul N+1 ascuns subtil într-un .map() cu async/await. În postarea asta îți arăt cum am folosit $metrics ca să prindem problema în staging și cum am refăcut interogările cu include fără să omorâm memoria Node.js.

Cum ajungi la N+1 în Prisma fără să-ți dai seama

Avem o schemă clasică: User -> Post -> Comment. De multe ori, văd în code review-uri cod de genul: faci un findMany pentru useri, iar apoi repeți o buclă async ca să tragi postările sau profilul fiecărui user.

Problemă mare e că Prisma pare că te protejează, oferind o sintaxă curată. Însă dacă apelezi un sub-query într-un Promise.all sau într-un loop for...of, clientul va trimite fiecare interogare separat către Postgres. La 100 de useri în baza de date, ai 1 query inițial + 100 query-uri secundare. Pool-ul de conexiuni se epuizează instant, conexiunile așteaptă la coadă, iar latența sare direct în aer.

Cum detectezi dezastrul folosind $metrics

În loc să stăm cu DEBUG="prisma:client:query" pornit pe dev (care umple terminalul de zgomot indescifrabil), am activat feature-ul de $metrics în instanța de Prisma Client. Îți dă cifre exacte despre câte interogări SQL s-au executat și latența lor.

Frecvent apelăm prisma.$metrics.json(). Acesta îți returnează un snapshot cu queries_total și query_duration_histogram_ms. Dacă observi că pentru un singur request HTTP numărul de queries_total crește liniar cu numărul de entități returnate din DB, ai oficial o problemă de N+1.

La noi pe proiect, am învelit asta într-un middleware de Fastify pe mediul de staging: dacă un singur request HTTP generează mai mult de 15 query-uri SQL, aruncăm o alertă în log-uri și picăm testul de e2e. Așa am prins 3 bug-uri înainte să ajungă în producție.

Soluția cu include smart și trade-off-ul cu memoria

Fix-ul rapid este să folosești include sau select direct pe query-ul principal. Prin asta, Prisma generează un SQL optimizat folosind JOIN sau face un batching intern inteligent prin IN (...), aducând totul într-un singur round-trip cu baza de date.

Dar atenție la trade-off! Când tragi include: { posts: { include: { comments: true } } }, elimini latența de rețea, dar riști să creezi un payload imens în RAM. Postgres îți va returna date duplicate pe fiecare rând de join. Am avut cazul real în care un răspuns JSON de 2MB în Node.js ne-a urcat consumul de RAM de la 150MB la 1.2GB sub sarcină, declanșând OOM (Out Of Memory) pe container.

Cum rezolvi asta deștept? Când folosești include sau select, adu doar câmpurile de care ai nevoie directă în UI și pune întotdeauna o limită (take: 5 sau paginare) pe relațiile nested.

Voi cum monitorizați interogările generate de ORM în producție? Bazați totul pe APM-uri externe gen Datadog sau aveți verificări automate direct în teste?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.