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

N+1 în Prisma: Cum îl prinzi cu $metrics și îl rezolvi cu smart select

De Bogdan Răducanu, 3 aug. 2026 · 8 vizualizări · 3 like-uri

Postat 3 aug. 2026
typescript
// BAD: N+1 Queries (50 comenzi = 51 interogări SQL)
const orders = await prisma.order.findMany({ take: 50 });
const fullOrders = await Promise.all(
  orders.map(async (order) => {
    const user = await prisma.user.findUnique({ where: { id: order.userId } });
    return { ...order, user };
  })
);

// GOOD: Single query cu select specific (fără over-fetching)
const optimizedOrders = await prisma.order.findMany({
  take: 50,
  select: {
    id: true,
    totalAmount: true,
    createdAt: true,
    user: {
      select: {
        id: true,
        email: true,
        name: true,
      },
    },
  },
});

Dacă folosești Prisma de ceva timp, probabil ai trăit cu impresia că ORM-ul rezolvă magic orice problemă de performanță. N-are cum. Anul trecut am prins în producție o chestie urâtă pe un proiect cu 15k utilizatori activi: o listă de comenzi trimitea peste 300 de interogări SQL la o singură încărcare de pagină.

Baza de date stătea în 90% CPU și noi ne întrebam de ce crapă API-ul când crește traficul. Era clasicul N+1, mascat sub câteva linii de cod TypeScript care arătau impecabil la code review.

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

Prisma are un mecanism destul de deștept de batching automat pentru findUnique, dar te păcălește ușor dacă ieși din tipare. Cea mai frecventă greșeală pe care o văd este maparea unui array și apelarea unei interogări asincrone pentru fiecare element în parte.

Să zicem că vrei să iei ultimele 50 de comenzi și, pentru fiecare comandă, să tragi detaliile despre client. Dacă faci un Promise.all peste array-ul de comenzi cu un prisma.user.findUnique în interior, ai distrus performanța. Prisma va executa 1 query pentru comenzi, plus încă 50 de query-uri separate pentru fiecare user. În total: 51 de round-trip-uri către baza de date.

La o latență de rețea de doar 2ms între serverul de Node.js și Postgres, doar timpul pierdut pe drum înseamnă peste 100ms irosiți complet degeaba.

Cum prinzi buba folosind $metrics

Nu poți optimiza ce nu măsori. Până să punem un APM dedicat în producție, cea mai rapidă metodă a fost să folosim $metrics direct din clientul de Prisma.

Prin prisma.$metrics.json(), primești un snapshot exact cu datele de execuție. Dacă vezi că numărul prisma_client_queries_total crește exponențial la o singură cerere HTTP, ai găsit suspectul.

În mediul de dezvoltare, eu îmi pun mereu log: ['query'] când instanțiez clientul. Dacă consola mea începe să spameze SELECT-uri aproape identice în timp ce se încarcă o singură pagină, e clar că am dat de un N+1.

Soluția: Smart include și select

Soluția directă este să folosești include pentru a aduce relațiile dintr-o singură mișcare. Prisma va genera un query optimizat folosind IN (...) sau un JOIN curat.

Dar atenție la trade-off-uri. include aduce toate coloanele din tabelul relaționat. Dacă aduci 100 de utilizatori și fiecare are un JSONB masiv în profil, ai rezolvat problema de rețea din DB, dar îți umpli memoria RAM din Node.js și omori garbage collector-ul.

Aici intervine un select structurat inteligent. În loc de include, folosești select atât pentru modelul principal, cât și pentru relații. Adaugi doar câmpurile de care ai strictă nevoie în Frontend.

La noi, după ce am înlocuit bucla de Promise.all cu un select bine calibrat, timpul de răspuns al endpoint-ului a scăzut de la 320ms la 14ms, iar încărcarea pe procesorul bazei de date a căzut sub 5%.

Concluzie

ORM-urile sunt excelente pentru viteză de dezvoltare, dar nu elimină necesitatea de a înțelege ce se întâmplă sub capotă. Folosiți select restrâns, verificați log-urile în dev și măsurați constant.

Voi cum monitorizați query-urile Prisma în producție? Folosiți Prisma Accelerate, un APM ca Datadog sau aveți un middleware custom?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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