import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export async function trackQueryCount(routeHandler: () => Promise<any>) {
const metricsBefore = await prisma.$metrics.json();
const queriesBefore = metricsBefore.counters.find(
(c) => c.key === 'prisma_client_queries_total'
)?.value ?? 0;
const result = await routeHandler();
const metricsAfter = await prisma.$metrics.json();
const queriesAfter = metricsAfter.counters.find(
(c) => c.key === 'prisma_client_queries_total'
)?.value ?? 0;
const diff = queriesAfter - queriesBefore;
if (diff > 15) {
console.warn(`[PERF ALERT] Endpoint-ul a rulat ${diff} query-uri! Posibil N+1.`);
}
return result;
}Prisma te lasă să livrezi features cu o viteză fantastică în primele luni, dar te taxează fără milă dacă nu ești atent la ce SQL scoate pe țeavă. Am pățit-o acum un an la un proiect SaaS cu vreo 12.000 de useri activi: un simplu tabel de facturi a trecut brusc de la 90ms la peste 3.2 secunde timp de răspuns. CPU-ul instanței de Postgres se apropia periculos de 95%.
Problema clasică: problema N+1 mascată în servicii curate de TypeScript.
Cum ajungi la N+1 fără să-ți dai seama
Mulți cred că dacă folosesc Prisma, ORM-ul le rezolvă automat batch-uirea. Parțial adevărat: dacă folosești relații nested în același apel, Prisma știe să facă interogări grupate folosind WHERE id IN (...).
Dezastrul apare când scrii cod modular și începi să spargi logica în mici funcții reutilizabile. Iei o listă de companii cu prisma.company.findMany(), apoi într-un Promise.all sau un map apelezi getLatestInvoice(company.id). Felicitări: ai generat un query pentru companii și încă 200 de query-uri individuale pentru fiecare factură în parte. În local pe un SQLite sau un Postgres de Docker cu 10 rânduri nu simți nimic. În staging cu latență de rețea între ECS și RDS, mori cu zile.
Cum detectezi problema cu prisma.$metrics
În mod tradițional, oamenii pun log: ['query'] în PrismaClient. E groaznic în staging sau producție fiindcă îți umple CloudWatch-ul sau Grafana Loki de zgomot inutil.
De la versiunea 4.2 încoace, Prisma are o metodă mult mai elegantă: $metrics. Îți oferă metrici Prometheus sau JSON direct din client, inclusiv contorul prisma_client_queries_total.
Ce fac eu într-un middleware de Fastify sau NestJS: capturez numărul de query-uri înainte de execuția handler-ului și fac diferența la final. Dacă un singur request HTTP execută mai mult de 15-20 de interogări, trimit un warning în loguri sau chiar trântesc un test în CI. La noi, testul ăsta a prins trei regrese majore înainte să ajungă vreodată pe mâna QA-ului.
Soluția: include smart vs. query-uri pe bucăți
Prima reacție e să trântești include: { invoices: true } peste tot. Atenție mare aici, fiindcă e un trade-off sincer:
- Ce câștigi: Scapi instant de N+1. Prisma trage companiile, apoi trage facturile printr-un singur query cu
INși le îmbină în memorie în Node.js. - Unde devine nasol:
includeaduce absolut toate coloanele dacă nu foloseștiselect. Dacă factura ta are câmpuri de audit, JSON-uri mari sau text lung, transferi megabiți de date degeaba prin rețea. În plus, Prisma consumă mult RAM când construiește obiecte imbricate mari.
Dacă relația e 1-la-mulți și datele sunt multe, folosește întotdeauna select imbricat și limitează strict câmpurile. Dacă ai nevoie de paginare pe relația copil (de exemplu, doar ultimele 3 facturi per companie), include nu te mai ajută direct fără subquery-uri brute sau Prisma Client Extensions.
Voi ce strategie aveți când Prisma începe să gâfâie pe relații complexe — mergeți pe raw SQL sau încercați să țineți totul în type safety-ul ORM-ului?