// Exemplu de monitorizare simplă cu $metrics
async function getDashboardData() {
// Resetăm metricile pentru a măsura doar acest request
// (util în development/staging)
const metrics = await prisma.$metrics.json();
const users = await prisma.user.findMany({
where: { active: true },
include: {
posts: {
select: {
id: true,
title: true
},
take: 5
}
}
});
const endMetrics = await prisma.$metrics.json();
const queryCount = endMetrics.counters.find(c => c.key === 'prisma_client_queries_total');
console.log(`Total query-uri executate: ${queryCount?.value}`);
return users;
}Salutare. Prisma e probabil cel mai 'prietenos' ORM pe care l-am folosit în ultimii ani, dar magia asta are un preț destul de mare dacă nu ești atent la ce se întâmplă sub capotă. Am pățit-o recent la un proiect cu vreo 12k useri activi, unde un endpoint de dashboard începuse să răspundă în aproape 2 secunde. Problema? Clasicul N+1, ascuns elegant în spatele unor proprietăți accesate într-un loop.
Faza e că Prisma te face să uiți că lucrezi cu o bază de date relațională. Scrii user.posts și te aștepți să meargă. Dacă ai 50 de useri și pentru fiecare vrei să afișezi ultimele postări, Prisma s-ar putea să facă un query pentru lista de useri și apoi încă 50 de query-uri separate pentru postările fiecăruia. În log-uri arată horror.
Cum detectezi dezastrul cu $metrics
Înainte să sari cu refactoring-ul, trebuie să știi exact unde te afli. Mulți developeri se bazează pe log-ul de query (log: ['query']), dar când ai trafic mare, consola aia devine imposibil de citit. Eu am început să folosesc prisma.$metrics.json().
E un tool super util care îți dă statistici agregate. Am făcut un mic middleware de monitorizare care, la finalul fiecărui request pe environment-ul de staging, îmi scuipă în consolă numărul total de query-uri SQL executate. Când am văzut că un singur hit pe /api/dashboard genera 151 de query-uri, m-am prins imediat că avem o problemă de tip loop. Practic, am economisit vreo 30% din resursele bazei de date doar prin identificarea acestor spike-uri de conexiuni inutile.
Rezolvarea cu include și select
Cea mai rapidă metodă de a scăpa de N+1 în Prisma este să folosești include. În loc să lași ORM-ul să facă fetch-ul leneș (lazy-ish), îi spui de la început: „Auzi, adu-mi userii, dar fă și un join sau un batch query pentru postări”.
Totuși, mare atenție la trade-off-uri. include e excelent pentru relații simple, dar dacă tabelul relaționat are 40 de coloane și ție îți trebuie doar title, folosește select în interiorul lui include. Altfel, muți problema din baza de date în memoria RAM a serverului de Node.js, pentru că vei parsa un JSON imens de care n-ai nevoie. La proiectul menționat, am trecut de la un include: { posts: true } la un select granular și am scăpat de un overhead de vreo 40MB per request pe heap-ul de V8.
Când 'include' devine periculos
Nu totul se rezolvă cu un include masiv. Am avut cazul unui raport unde trebuia să aduc date din 5 tabele relaționate. Un include adânc pe 4-5 nivele a generat un SQL atât de complex încât Postgres-ul a decis că e mai ieftin să facă un Sequential Scan în loc să folosească indexii.
În momentele alea, am făcut un pas în spate și am folosit Promise.all cu query-uri separate sau chiar Prisma.sql (raw query). Uneori, e mai performant să faci 2 query-uri optimizate manual decât unul singur generat de ORM care are 300 de linii de SQL cu 10 join-uri.
Morala e simplă: Prisma e un instrument, nu o scuză să nu știi SQL. Dacă nu monitorizezi numărul de query-uri, te vei trezi cu facturi imense la RDS sau cu un server care gâfâie fără motiv aparent. Voi cum vă monitorizați performanța query-urilor în producție?