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:
- Folosește operatorul
inpentru a aduce toate resursele părinte într-un singur call. - Folosește
includesau mai bineselectpentru 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?