// 1. Cum detectăm problema folosind $metrics
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient({ log: ['query'] });
async function getMetrics() {
const metrics = await prisma.$metrics.json();
const queryCount = metrics.counters.find(c => c.name === 'prisma_client_queries_total');
console.log(`Query-uri rulate până acum: ${queryCount?.value}`);
}
// 2. Fix-ul: Trecerea de la N+1 la o interogare optimizată
async function getOrdersOptimized() {
return await prisma.order.findMany({
take: 50,
select: {
id: true,
totalPrice: true,
// Evităm include: true și selectăm doar ce avem nevoie ca să nu încărcăm memoria
user: {
select: {
id: true,
email: true
}
}
}
});
}Prisma e excelent pentru rapiditatea cu care scrii cod, dar ascunde baza de date atât de bine încât te trezești cu N+1 query-uri fără să-ți dai seama. Am pățit asta recent pe un proiect cu peste 15.000 de useri activi, unde un simplu dashboard bloca event loop-ul. În postarea asta îți arăt cum să prinzi aceste interogări ascunse folosind $metrics și cum să le rezolvi curat.
Cum am dat de problemă în staging
Ne miram de ce o pagină care lista doar 50 de comenzi făcea aproape 3 secunde să se încarce. Analizând logurile, am văzut un comportament bizar. Pentru fiecare comandă returnată, Prisma făcea câte un query separat în baza de date ca să aducă detaliile clientului și adresa de livrare. În loc de o singură interogare mare cu JOIN-uri, aveam 1 + 50 + 50 de query-uri individuale. SQL-ul plângea în hohote în RDS, iar conexiunile se epuizau instant.
Detectarea rapidă cu $metrics
În loc să punem tool-uri grele de APM direct în faza de development, am folosit ce ne oferă Prisma direct "out of the box". Poți activa metricile în instanța de client și să le expui pe un endpoint intern de monitorizare sau să le loghezi la finalul request-ului.
Dacă valoarea pentru prisma_client_queries_total crește exponențial cu numărul de iteme returnate de API-ul tău, ai o problemă clară de tip N+1. Noi am scris un mic middleware pentru Express/NestJS care scuipă această valoare în consolă pe mediul de test.
Rezolvarea cu include (și de ce nu select simplu)
Cea mai rapidă metodă de rezolvare este să folosești include pentru relații, forțând Prisma să facă un singur query combinat sau o interogare optimizată prin batching (folosind sub-interogări în spate).
Dar atenție la un trade-off major. Dacă folosești include orbește (cum ar fi include: { user: true }), aduci absolut toate coloanele din tabela asociată, inclusiv hash-ul de parolă, adrese secundare sau JSON-uri masive. La un tabel cu zeci de coloane, asta înseamnă un consum uriaș de memorie în Node.js.
Regula mea de aur: folosește select nișat în interiorul relației. În loc de include, folosește un select imbricat pentru a aduce doar ID-ul și numele utilizatorului. Astfel, reduci și numărul de query-uri la 1 (sau 2 optimizate), dar păstrezi și payload-ul din memorie extrem de mic.
Voi cum vânați query-urile ineficiente în producție? Mergeți pe logurile brute din Postgres/MySQL sau folosiți APM-uri dedicate?