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

Cum am scăpat de problema N+1 în Prisma folosind $metrics și include chibzuit

De Elena Dumitrescu, 18 sept. 2026 · 18 vizualizări · 3 like-uri

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

// Măsurare rapidă a numărului de query-uri generate per endpoint
export async function inspectEndpointQueries() {
  const before = await prisma.$metrics.json();
  const initialQueries = before.counters.find(
    (c) => c.key === 'prisma_client_queries_total'
  )?.value ?? 0;

  // Query optimizat: select restrâns + relationLoadStrategy
  const orders = await prisma.order.findMany({
    take: 50,
    select: {
      id: true,
      createdAt: true,
      address: {
        select: { city: true, street: true }
      }
    },
    relationLoadStrategy: 'join' // Disponibil în Prisma 5+
  });

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

  console.log(`Query-uri executate: ${finalQueries - initialQueries}`);
  return orders;
}

Pe localhost totul zboară. Ai 10 useri în baza de date, rulezi un loop asincron sau un include neatent și pagina se randează în 15 milisecunde. Problema apare când ajungi în producție cu date reale și câteva mii de useri activi simultan.

Am pățit treaba asta acum un an pe un microserviciu de comenzi. La un vârf de 12k cereri pe oră, baza de date urcase la 98% CPU, iar connection pool-ul era complet uscat. Cauza clasică: un clasic N+1 mascat frumos în codul de TypeScript de care nimeni nu se prinsese la code review.

Cum m-am prins: Prisma $metrics

Mulți dezvoltatori lasă Prisma să ruleze orbește în producție și se bazează doar pe APM-ul de la Datadog sau New Relic. Dar Prisma are o unealtă internă excelentă de la versiunea 4 încoace: $metrics.

În loc să inspectez manual 200 de loguri de SQL generate de DEBUG="prisma:query", am expus temporar metricile într-un interceptor de Fastify. Rulând un singur request către ruta buclucașă, am văzut direct contorul prisma_client_queries_total. În loc de o singură interogare, făceam 1 + 120 de query-uri către tabela de adrese de livrare.

Când rulezi un cod gen orders.map(async (order) => prisma.address.findUnique(...)), ai impresia că Promise.all te salvează. În realitate, nu faci decât să tragi 120 de query-uri simultane care blochează pool-ul de conexiuni.

Rezolvarea: include inteligent și limitările lui

Prima reacție reflexă este să pui include: { address: true } pe query-ul de părinte. Prisma rezolvă asta elegant sub capotă: în loc de N+1 interogări individuale, execută de obicei două query-uri: unul pentru părinte și unul de tip WHERE id IN (...) pentru relație.

Dar atenție la capcană: include aduce absolut toate coloanele. Dacă tabela de adrese are 15 câmpuri și tu ai nevoie doar de oraș și stradă, tragi inutil sute de kiloocteți prin memorie și rețea. În plus, dacă ai relații nested (de exemplu comandă -> user -> profil -> avatar), volumul de date explodează.

Soluția mai curată este folosirea lui select compus:

  • Tragi doar câmpurile strict necesare din tabela părinte.
  • Faci select dedicat pe relație.
  • Dacă ai Prisma 5.x+, testează flag-ul preview relationLoadStrategy: "join". În loc de 2-3 query-uri separate legate prin IN, Prisma va compila totul într-un singur SQL cu LEFT JOIN real.

Trade-off-ul pe care trebuie să-l asumi

Join-urile reale în SQL reduc latența de rețea fiindcă ai un singur roundtrip către serverul de bază de date. Totuși, dacă relația este de tip 1-la-mulți și ai multe rânduri copil, serverul de Postgres va dubla datele din părinte pentru fiecare rând copil (efectul de produs cartezian). Asta crește consumul de RAM pe procesul de Node.js când Prisma parsează rezultatul.

Eu prefer regula asta: pentru relații 1-la-1 folosesc direct relationLoadStrategy: "join". Pentru relații 1-la-N cu volume mari de copii, las strategia default Prisma cu IN queries, fiindcă menajează memoria aplicației.

Voi cum monitorizați query-urile dubioase în producție? Vă bazați pe metrici la nivel de ORM sau verificați direct pg_stat_statements în Postgres?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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