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

N+1 în Prisma: Cum le detectezi cu $metrics și cum le rezolvi curat

De Răzvan Matei, 28 iul. 2026 · 9 vizualizări · 3 like-uri

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

const prisma = new PrismaClient({
  rejectOnNotFound: false,
});

// 1. Cum extragi metricile pentru a detecta spike-uri de interogări
export async function getPerformanceReport() {
  const metricsJson = await prisma.$metrics.json();
  const queryCount = metricsJson.counters.find(
    (c) => c.key === 'prisma_client_queries_total'
  );
  console.log(`Query-uri executate până acum: ${queryCount?.value}`);
}

// 2. Ocolirea N+1 cu SELECT smart (fără over-fetching)
export async function getOrdersOptimized(userId: string) {
  return prisma.order.findMany({
    where: { userId },
    select: {
      id: true,
      createdAt: true,
      items: {
        select: {
          quantity: true,
          product: {
            select: { name: true, price: true } // Nu aducem descrierea lungă sau imginile
          }
        }
      }
    }
  });
}

Salutare. Am pățit-o recent pe un serviciu unde gestionăm în jur de 15.000 de comenzi pe lună. Endpoint-ul de export rula perfect pe mediul de staging, dar în producție latency-ul a sărit brusc de la 120ms la peste 4 secunde. Sursa problemei? O interogare N+1 mascată elegant de o buclă map() async pe care un coleg a trecut-o neobservată la code review.

Prisma te răsfață cu un API curat, dar abstracția asta te face uneori să uiți ce se întâmplă sub capotă în SQL.

Cum te prinde Prisma în capcană

Spre deosebire de ORM-uri mai vechi, Prisma încearcă să facă un lucru deștept numit automatic query batching. Dacă apelezi prisma.user.findUnique() în mod repetat într-un Promise.all(), Prisma va încerca să le grupeze automat într-un singur WHERE id IN (...).

Problema apare când iterezi peste o listă și faci interogări dependente de rezultatul anterior (de exemplu, cauți fiecare comandă și apoi tragi produsele pentru fiecare în parte). În acel moment, batching-ul automat pică, iar codul tău execută 1 interogare inițială plus încă N interogări separate către baza de date.

La 50 de elemente în dev nu simți nimic. La 15.000 de rânduri în producție, conexiunile din pool se epuizează și baza de date intră în panică.

Profiling direct din cod folosind $metrics

Nu ai nevoie de un APM scump precum Datadog doar ca să prinzi N+1-uri în staging. Prisma include nativ un modul de metrici (disponibil prin $metrics) care îți oferă un snapshot exact al interogărilor.

Îl poți activa în clientul tău și poți extrage metricile în format JSON sau Prometheus. Dacă vezi prisma_client_queries_total crescând cu sute de unități la un singur request HTTP, ai un indicator clar de N+1.

Soluția: include smart vs. over-fetching

Cea mai simplă rezolvare este să folosești include sau select direct în query-ul principal. Astfel, Prisma va folosi fie un JOIN eficient, fie un query secundar optimizat cu IN.

Aici apare trade-off-ul sincer: include rezolvă problema latenței de rețea (fără round-trips inutile), dar poate genera o altă problemă gravă — over-fetching.

Am avut un caz în care un include: { orderItems: { include: { product: true } } } a adus peste 60 MB de date nefolosite în memoria procesului Node.js. Rezultatul? Garbage collection spikes de 800ms și Node.js în pericol de OOM (Out of Memory).

Regula de aur pe care o aplicăm acum: dacă ai nevoie doar de 2-3 câmpuri dintr-o relație, folosește întotdeauna select imbricat în loc de include: true. Salvezi și rețea, și memorie RAM.

Voi cum monitorizați interogările SQL generate de Prisma înainte să ajungă în producție? Folosiți query logging în CI/CD sau vă bazați pe metrici în APM?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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