import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// 1. Cum extragem metricile pentru a depista N+1
async function logQueryMetrics() {
const metrics = await prisma.$metrics.json();
const queryCount = metrics.counters.find(
(c) => c.name === 'prisma_client_queries_total'
)?.value || 0;
if (queryCount > 15) {
console.warn(`[Warning] Posibil N+1 detectat! S-au rulat ${queryCount} query-uri.`);
}
}
// 2. Soluția optimizată: aducem relațiile într-un singur pas
async function getActiveUsersWithPosts() {
return await prisma.user.findMany({
where: { active: true },
include: {
posts: {
select: {
id: true,
title: true // Nu aducem tot body-ul postării degeaba
}
}
}
});
}Am pățit-o acum un an la un proiect cu vreo 12.000 de utilizatori activi pe zi. Din senin, CPU-ul bazei de date a sărit la 98% și totul a început să se miște execrabil. Vinovatul? Clasica problemă N+1, ascunsă adânc într-un loop de Prisma unde mapam niște relații fără să-mi dau seama ce se întâmplă sub capotă.
Cum naiba apare N+1 în Prisma?
ORM-urile ne fac leneși. E un adevăr dureros, dar real. În Prisma, e extrem de ușor să scrii un cod care pare curat în editor, dar care generează un adevărat dezastru în spate. De exemplu, când iei o listă de utilizatori și apoi, pentru fiecare utilizator, rulezi o interogare separată ca să-i aduci postările sau profilul.
Dacă ai 100 de utilizatori, codul tău va face 1 query inițial pentru utilizatori, iar apoi încă 100 de query-uri separate. Total: 101 interogări la baza de date pentru a afișa o simplă listă pe un dashboard. Când ai 10 useri în baza de date de test nu observi nimic. Când treci în producție și ai mii de înregistrări, baza de date începe să gâfâie.
Detecția cu $metrics (fără ghicit)
Ca să rezolvi o problemă, mai întâi trebuie să știi că o ai. Nu vrei să stai cu ochii în terminal pe log-urile de query-uri când rulezi aplicația local. De la versiunea 4.7.0 încoace, Prisma vine cu o funcționalitate excelentă numită $metrics.
Am implementat un middleware simplu în API-ul nostru care extrage numărul de query-uri rulate în timpul fiecărui request HTTP. Dacă numărul total de query-uri SQL sare de 15 pentru o singură rută, trimitem o alertă în consolă sau în Slack.
Trade-off-ul sincer aici? Activarea metricilor în producție adaugă un mic overhead de memorie și cam 1-2ms per request pentru că Prisma trebuie să contorizeze intern fiecare operațiune. Pentru noi, acest cost a fost complet neglijabil comparat cu avantajul de a vedea instant când un coleg a introdus din greșeală un query în loop.
Soluția: include-ul smart și select-ul
Cea mai rapidă cale de a elimina N+1 este să folosești include direct în interogarea principală. Prisma este destul de inteligentă sub capotă. Când folosești include, ea nu face neapărat un JOIN SQL gigant (care uneori poate fi ineficient pe tabele mari), ci execută de obicei două interogări optimizate: una pentru tabela principală și una cu operatorul IN pentru relații, combinând apoi datele în memorie.
Totuși, atenție la un alt trade-off: consumul de RAM în Node.js. Dacă faci include pe relații mari fără să filtrezi, aduci o tonă de date inutile din baza de date direct în memoria procesului Node.js. Regula de aur pe care o aplic acum este să combin mereu include sau select cu filtre stricte, aducând doar ID-urile și câmpurile esențiale pentru UI.
Voi cum monitorizați query-urile astea ascunse în producție? Mergeți pe APM-uri clasice gen Datadog sau v-ați scris tool-uri custom peste metricile din Prisma?