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

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

De Dan Ciobanu, 27 iul. 2026 · 9 vizualizări · 3 like-uri

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

const prisma = new PrismaClient();

// 1. Verificare metrics pentru debug
export async function getMetrics() {
  return await prisma.$metrics.json();
}

// 2. Query optimizat cu join strategy (Prisma 5.x+)
export async function getOrdersWithCustomer() {
  return await prisma.order.findMany({
    take: 50,
    relationLoadStrategy: 'join', // Forțează un singur query SQL cu JOIN
    include: {
      customer: true,
    },
  });
}

Anul trecut am avut o problemă urâtă pe un dashboard de e-commerce cu vreo 12.000 de comenzi pe lună. Endpoint-ul de /admin/orders începuse să răspundă în 4.2 secunde, iar CPU-ul pe baza de date sărea scurt în 90%. Clasic N+1, dar îmbrăcat în haine frumoase de TypeScript.

Prisma e un ORM genial pentru DX, dar te fură peisajul repede. Când scrii cod async în bucle sau apelezi funcții helper care își trag singure dependențele, te trezești că pentru o listă de 50 de elemente execuți 51 de query-uri în Postgres fără să-ți dai seama.

Cum am depistat buba: Prisma $metrics

Majoritatea devilor pun log: ['query'] în opțiunile de PrismaClient și se uită în terminal cum curg sute de linii. E ok pe local când ai 5 rânduri în DB. În producție sau pe staging cu date reale, log-urile alea devin doar un zgomot inutil.

De la versiunea 4.10 încoace, Prisma are $metrics. E o opțiune mult mai curată pentru că îți dă date agregate despre query-uri, durată și conexiuni.

Trebuie doar să apelezi prisma.$metrics.json() pe un endpoint de health sau admin. Vei vedea imediat metrice precum prisma_client_queries_total. Când observi că numărul total de interogări crește liniar cu numărul de entități afișate în pagină, ai prins clar un N+1.

Soluția: include vs. relationLoadStrategy

Varianta rapidă de rezolvare este să folosești include direct pe interogarea principală. În loc să faci un .findMany() și apoi un .map() cu await pe fiecare item, îi spui ORM-ului să-ți aducă relația din prima.

Implicit, Prisma rezolvă include-ul prin două interogări SQL separate: una pentru părinte și una cu WHERE id IN (...) pentru copii. În 95% din cazuri, batching-ul ăsta implicit e suficient și reduce timpul de la câteva secunde la sub 100ms.

În versiunile recente de Prisma au introdus și relationLoadStrategy: 'join'. Asta obligă Prisma să genereze un singur query SQL cu LEFT JOIN la nivel de bază de date, sărind peste pasul de in-memory batching.

Trade-off sincer: atenție la produsul cartezian

Aici mulți își fură căciula. JOIN-urile sau include-urile deeply nested (gen orders -> items -> product -> category) nu sunt întotdeauna un glonț de argint.

Dacă aduci 100 de comenzi și fiecare are 20 de produse, un JOIN direct va duce la un set de date uriaș returnat de Postgres. Am pățit să scot 150ms latență pe DB, dar să blochez Event Loop-ul din Node.js timp de 400ms doar pentru că V8 trebuia să facă parsing pe un JSON imens de duplicate.

Uneori, două query-uri separate cu IN sunt mult mai rapide și consumă de zece ori mai puțină memorie decât un JOIN gigantic.

Voi cum monitorizați interogările generate de Prisma în producție? Folosiți $metrics exportat în Prometheus sau mergeți pe APM-uri externe?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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