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

N+1 queries în Prisma: Cum le depistezi cu $metrics și cum le rezolvi inteligent

De Elisabeta Stan, 24 mai 2026 · 3 vizualizări · 2 like-uri

Postat 24 mai 2026
typescript
// Cum arată un query optimizat cu select imbricat și limitare pentru a evita supraîncărcarea
const usersWithLatestPosts = await prisma.user.findMany({
  take: 20,
  select: {
    id: true,
    email: true,
    posts: {
      take: 5, // Evităm să tragem mii de postări în memoria Node
      select: {
        id: true,
        title: true,
      },
    },
  },
});

// Cum extragi rapid metricile de query count în middleware sau router
const metrics = await prisma.$metrics.json();
const queryCount = metrics.counters.find(c => c.key === 'prisma_client_queries_total')?.value;
console.log(`Total query-uri rulate pentru acest request: ${queryCount}`);

Am dat recent peste o problemă clasică de performanță pe un proiect cu vreo 12.000 de utilizatori activi, unde baza de date începuse să gâfâie din cauza interogărilor N+1 generate de Prisma. Dacă folosești Prisma, probabil știi că e super ușor să cazi în capcana asta când randezi liste complexe. În postarea asta îți arăt cum să le depistezi rapid folosind $metrics și cum să le rezolvi elegant fără să strici lizibilitatea codului.

Cum m-am prins că avem o problemă

Eram în faza de optimizare a unui dashboard de analytics. Pagina se încărca în aproape 4 secunde, ceea ce e enorm pentru doar câteva sute de rânduri randate. Am activat logging-ul de query-uri în Prisma, dar logurile din consolă deveniseră imposibil de urmărit la volumul ăla de date.

Așa că am apelat la Prisma $metrics. E o funcție integrată, dar destul de puțin folosită, care îți dă un snapshot clar peste ce se întâmplă sub capotă. Am pus un middleware simplu care scuipă metricile în consolă la finalul requestului.

Când am văzut că pentru o singură pagină de listare aveam peste 150 de interogări SQL rulate în mai puțin de o secundă, mi-a fost clar: clasica problemă N+1. Prisma trimitea un query să ia utilizatorii, iar apoi, într-un loop (adesea ascuns în map-urile din codul de prezentare sau în resolverele de GraphQL), trimitea câte un query separat pentru profilul și postările fiecăruia.

Soluția: include și select cu cap

Cea mai simplă metodă de a opri masacrul din baza de date este să folosești include sau select direct în query-ul principal. Prisma știe să facă join-urile necesare sau query-uri batch optimizate, în funcție de relație, într-un singur pas.

Dar atenție la trade-off-ul de memorie. Dacă faci include: { posts: true } pe o relație de tip one-to-many unde fiecare user are mii de postări, o să îți explodeze consumul de memorie în Node.js. Soluția mea a fost să limitez câmpurile returnate folosind un select imbricat și să pun o limită de paginare chiar și în relațiile incluse.

Am trecut de la 150+ query-uri la doar 2 query-uri optimizate (unul pentru useri, unul pentru relațiile lor, grupate prin IN). Timpul de răspuns al API-ului a scăzut de la 4 secunde la 120ms. Am redus încărcarea pe baza de date cu aproape 70%.

Trade-off-ul de care nu vorbește multă lume

E important de înțeles cum funcționează Prisma sub capotă. Spre deosebire de un ORM tradițional ca TypeORM sau Sequelize care face un LEFT JOIN masiv în SQL, Prisma preferă de multe ori să ruleze query-uri separate dar optimizate (de exemplu, selectează părinții, apoi rulează un al doilea query cu WHERE id IN (...) pentru copii).

Asta e super ok pentru că evită "cartesian product" (baza de date nu returnează date duplicate pe rânduri), dar înseamnă că tot ai 2-3 query-uri în loc de unul singur. Pentru majoritatea aplicațiilor, abordarea asta e ideală. Totuși, dacă ai latență mare între serverul de Node și baza de date (de exemplu, sunt în regiuni diferite), chiar și acele 2 query-uri optimizate pot să doară. Asigură-te că serverul și baza de date sunt în aceeași rețea fizică.

Voi cum monitorizați interogările astea în producție? Mergeți pe $metrics sau folosiți un tool extern gen OpenTelemetry?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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