eduardweb.
PerformanceIntermediar#performance#typescript#prisma#postgresql#databases

Cum am rezolvat N+1 în Prisma cu $metrics și include/select smart

De Elisabeta Stan, 8 aug. 2026 · 4 vizualizări · 2 like-uri

Postat 8 aug. 2026
typescript
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

// 1. Inspectare metrici pentru a detecta N+1
async function logPrismaMetrics() {
  const metrics = await prisma.$metrics.json();
  console.log('Query counters:', metrics.counters);
}

// 2. Rezolvarea corectă: un singur query cu select specific
async function getOrdersOptimized() {
  return await prisma.order.findMany({
    take: 50,
    select: {
      id: true,
      createdAt: true,
      total: true,
      user: {
        select: {
          id: true,
          email: true, // evităm overfetching-ul
        },
      },
    },
  });
}

Luna trecută am dat peste o problemă urâtă pe un serviciu care deservea în jur de 12.000 de utilizatori zilnici. Dashboard-ul se încărca penibil de greu, baza de date gâfâia la 90% CPU, iar răspunsurile pe API săriseră de la 40ms la aproape 800ms. Vinovatul? Un N+1 clasic ascuns într-un map async peste un query Prisma.

Unde se ascunde N+1 când folosești Prisma

Prisma te face foarte rapid la scris cod, dar îți fură mințile dacă nu ești atent la ce se execută sub capotă. Scenariul tipic pe care îl văd des în code review-uri: preiei o listă de 100 de comenzi și, pentru fiecare comandă, faci un await prisma.user.findUnique() separat ca să îi aduci profilul.

Rezultatul este dezastruos: un query inițial pentru comenzi plus încă 100 de query-uri individuale pentru utilizatori. Ai 101 interogări la o singură încărcare de pagină. În mediul de dev cu 5 rânduri în SQLite totul pare instant. În producție, când ai mii de intrări și latență de rețea între pod-ul de Node.js și Postgres, dă bușonul.

Cum prinzi problema cu $metrics

În loc să instalezi APM-uri scumpe sau să ghicești prin loguri uriașe, Prisma are de ceva timp un feature excelent: $metrics. Poți extrage metrici directe despre câte interogări SQL se fac și cât durează fiecare opțiune.

Poți inspecta JSON-ul generat de Prisma direct într-un middleware de Fastify/Express sau într-un endpoint intern de health check. Dintr-o dată vezi negru pe alb că un singur request HTTP a declanșat peste 200 de interogări SQL identice.

Soluția: include smart și trade-off-ul de memorie

Cea mai simplă rezolvare este să renunți la buclele asincrone și să folosești include sau select direct în query-ul principal. Prisma va converti asta într-un query optimizat cu JOIN sau într-o selecție de tip WHERE id IN (...) executată intern extrem de rapid.

Am rescris codul buclucaș, am eliminat map-ul și am adăugat un select structurat. Latența pe endpoint a scăzut instant de la 800ms la 35ms, iar CPU-ul din baza de date a revenit la un tăcut 5%.

Totuși, există un trade-off pe care mulți seniori îl omit: overfetching-ul de memorie. Dacă pui include: { user: true } pe un tabel cu 40 de coloane și aduci 5.000 de rânduri, muți presiunea din DB direct în RAM-ul procesului Node.js. Payload-ul JSON devine uriaș, iar Garbage Collector-ul va începe să agate procesul.

Regula de aur pe care o aplic: folosește aproape mereu select în loc de include și cere doar câmpurile de care ai strictă nevoie în UI (de exemplu: id, email, name), nu tot obiectul.

Voi cum monitorizați query-urile N+1 în producție — aveți teste de integrari cu query counter sau vă bazați doar pe alerte de latență?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.