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

Cum am prins și rezolvat un N+1 masiv în Prisma folosind $metrics

De Ana Ionescu, 5 aug. 2026 · 7 vizualizări · 3 like-uri

Postat 5 aug. 2026
typescript
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

// ❌ RĂU: Generează N+1 query-uri (câte un query per user)
export async function getProjectsBad(userIds: string[]) {
  return Promise.all(
    userIds.map(id => prisma.project.findMany({ where: { userId: id } }))
  )
}

// ✅ BINE: Un singur query cu filter IN și select specific
export async function getProjectsGood(userIds: string[]) {
  return prisma.user.findMany({
    where: { id: { in: userIds } },
    select: {
      id: true,
      name: true,
      projects: {
        select: { id: true, title: true, status: true }
      }
    }
  })
}

// Extragere metrici pentru debug/telemetrie
export async function getMetrics() {
  const metrics = await prisma.$metrics.json()
  return metrics.counters
}

M-am lovit luna trecută de o problemă clasică la un API care deservea vreo 12k utilizatori activi: latența pe un endpoint simplu de listare a crescut brusc de la 45ms la peste 800ms. Baza de date (Postgres pe RDS) stătea la 20% CPU, dar conexiunile din pool se epuizau instant.

Cauza? Un N+1 masiv mascat într-un helper scris „curat” de un coleg mai junior. În loc să aducem datele dintr-o singură interogare, codul făcea un Promise.all() peste un array de ID-uri, executând câte o interogare SQL separată pentru fiecare entitate în parte.

Cum detectezi N+1 fără să umpli consola de loguri

Prima reacție când suspectezi probleme de SQL e să pui log: ['query'] în constructorul Prisma. Problemă: dacă ai 200 de request-uri pe secundă, consola devine o cascadă ilizibilă de text din care nu înțelegi nimic.

Soluția mult mai curată este să folosești opțiunea de $metrics inclusă în Prisma (disponibilă nativ din v4.x). Îți oferă metrici agregate direct în format Prometheus sau JSON, fără overhead vizual sau flood în stdout.

Dacă vezi că valoarea prisma_client_queries_total crește cu 50-100 la o singură apelare de endpoint, ai găsit problema. Metricile îți arată și timpul petrecut în wait (așteptarea unei conexiuni libere din pool), ceea ce confirmă că aplicația ta blochează event loop-ul așteptând răspunsuri irosite de la baza de date.

Soluția: de la bucle async la include și select inteligent

Prisma are un mecanism intern care face batching automat pentru findUnique, dar dacă folosești findMany sau structuri mai complexe într-un .map(), acest optimizor este bypassat complet.

Ca să rezolvi problema, trebuie să regândești fetch-ul dinspre baza de date:

  1. Folosește operatorul in pentru a aduce toate resursele părinte într-un singur call.
  2. Folosește include sau mai bine select pentru a aduce relațiile conexe într-o singură interogare eficientă executată de Query Engine-ul Prisma.

Atenție însă la trade-off-ul clasic: nu face include orbește pe tot ce prinzi. Dacă aduci o relație comments pe un model posts care are mii de intrări, poți ușor să dai crash Node.js-ului din lipsă de memorie RAM. JOIN-ul uriaș aduce megabiți buni de date pe care Prisma trebuie să-i deserializeze în obiecte JS. Regula mea de aur: folosește întotdeauna select și cere exact câmpurile de care ai nevoie pe frontend.

După ce am refăcut query-ul folosind where: { id: { in: ids } } combinat cu un select strict pe câmpurile necesare, numărul de query-uri executate per request a scăzut de la 121 la fix 1. Latența a scăzut înapoi la 38ms și am eliberat 80% din conexiunile din pool-ul RDS.

Voi cum monitorizați interogările generate de ORM-uri în producție? Bazați totul pe APM-uri gen Datadog/NewRelic sau aveți verificări automate în CI/CD pentru numărul de query-uri?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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