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

N+1 queries în Prisma: Cum le detectezi cu $metrics și când te salvează include

De Maria Vasilescu, 15 iun. 2026 · 23 vizualizări · 2 like-uri

Postat 15 iun. 2026
typescript
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

async function measureQueries(action: () => Promise<any>) {
  const before = await prisma.$metrics.json();
  const beforeQueries = before.counters.find(c => c.name === 'prisma_client_queries_total')?.value || 0;

  const result = await action();

  const after = await prisma.$metrics.json();
  const afterQueries = after.counters.find(c => c.name === 'prisma_client_queries_total')?.value || 0;

  const diff = Number(afterQueries) - Number(beforeQueries);
  console.log(`[Metrics] Action executed ${diff} SQL queries.`);
  return result;
}

Salutare! Prisma e genială până când nu mai e. Chestia asta cu "scapi de SQL scris de mână" vine la pachet cu o problemă veche de când lumea: interogările N+1. Am pățit-o acum vreo 6 luni pe un proiect cu 12k de useri activi zilnic, unde un singur endpoint de dashboard începuse să îngenuncheze baza de date. CPU-ul pe RDS se ducea în 90% din senin.

Cum am dat de dracu' cu 12k request-uri

Problema la Prisma e că nu are lazy loading clasic ca în Hibernate, dar te lasă să faci prostii la fel de mari. Cel mai des văd cod de genul: iei o listă de utilizatori și apoi, într-un map cu Promise.all, apelezi prisma.post.findMany pentru fiecare user în parte ca să îi aduci postările.

Dacă ai 100 de useri, faci 1 interogare inițială și încă 100 de interogări separate în baza de date. Asta e definiția de manual pentru N+1. Prisma are un motor de batching intern (DataLoader pattern), dar dacă schimbi puțin structura sau pui logica în servicii diferite, batching-ul ăla moare silențios.

Detecția: Cum prinzi N+1 înainte să plângă DevOps-ul

În loc să stai cu ochii pe logurile din PostgreSQL, poți folosi clientul de Prisma ca să îți spună exact câte query-uri rulează pe bune sub capotă. Pentru asta ne folosim de $metrics.

Am scris o metodă simplă pe care o rulăm în mediul de staging. Aceasta verifică numărul de query-uri înainte și după execuția unui serviciu sau controller. Dacă un singur request HTTP face mai mult de 10 query-uri în DB, testele noastre de integrare crapă direct. Am economisit cam 30% la build time și resurse pe server doar eliminând aceste apeluri redundante.

Rezolvarea: Include inteligent și select

Cea mai simplă metodă de rezolvare e să folosești include pentru a aduce relațiile într-un singur query (sau într-un query optimizat de tip batch pe care Prisma îl face automat în spate prin sub-select-uri).

// Greșit: generează N+1 dacă e apelat în loop
const posts = await prisma.post.findMany({ where: { userId } });

// Corect: aduce totul batch-uit
const usersWithPosts = await prisma.user.findMany({
  include: {
    posts: true,
  },
});

Trade-off-ul sincer: include te poate lăsa fără memorie

Sună simplu, nu? Pui include peste tot și gata, ai rezolvat performanța. Ei bine, nu.

Aici e capcana: include aduce absolut toate coloanele din tabela relaționată. Dacă tabela posts are un câmp de tip content de tip TEXT de câțiva megabytes, o să umpli memoria Node.js imediat. Am avut un caz în care un simplu include pe o relație de loguri a dublat consumul de RAM al containerului de backend în 10 minute.

Soluția? Folosește întotdeauna select în interiorul lui include ca să aduci doar id-ul, titlul sau ce ai strict nevoie pentru UI.

Voi cum monitorizați query-urile astea în producție? Vă bazați pe APM-uri externe sau aveți chestii custom în cod?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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