eduardweb.
PerformanceIntermediar#nodejs#performance#prisma#postgresql

N+1 în Prisma: cum le prinzi cu $metrics și le rezolvi curat

De Paul Ene, 30 iul. 2026 · 9 vizualizări · 2 like-uri

Postat 30 iul. 2026
typescript
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient({
  metrics: { format: 'json' }
});

// ❌ GREȘIT: Generează N+1 query-uri la DB
async function getBadFeed(userIds: string[]) {
  const users = await prisma.user.findMany({ where: { id: { in: userIds } } });
  return Promise.all(
    users.map(async (u) => {
      const posts = await prisma.post.findMany({ where: { authorId: u.id } });
      return { ...u, posts };
    })
  );
}

// ✅ CORECT: 2 query-uri optimizate + select strict
async function getGoodFeed(userIds: string[]) {
  return prisma.user.findMany({
    where: { id: { in: userIds } },
    select: {
      id: true,
      email: true,
      posts: {
        select: { id: true, title: true, createdAt: true }
      }
    }
  });
}

// Extragere metrici pentru debug
async function printMetrics() {
  const jsonMetrics = await prisma.$metrics.json();
  console.log('Query Count:', jsonMetrics.counters.find(c => c.key === 'prisma_client_queries_total')?.value);
}

Am pățit-o acum 6 luni pe un serviciu care deservea în jur de 12k cereri pe minut. Baza de date PostgreSQL ajunsese brusc la 95% CPU, deși codul arăta curat și interogările păreau simple. Buba? Un N+1 ascuns subtil într-un endpoint de feed, combinat cu un .map() asincron de care uitase cineva în PR.

De ce e Prisma periculos dacă mergi pe pilot automat

Prisma e excelent pentru developer experience, dar Query Engine-ul lui nu face minuni dacă îl folosești greșit. Când ai o listă de 50 de utilizatori și faci un Promise.all(users.map(u => prisma.post.findMany(...))), Prisma trimite 50 de query-uri separate către DB. Nu le unifică magic într-un singur batch de fiecare dată.

La noi, pe un set de date cu 100 de items per pagină, latența a sărit direct de la 35ms la peste 600ms. Conexiunile din pool se epuizau instant.

Cum le detectezi rapid cu $metrics

Înainte să arunci banii pe APM-uri scumpe, Prisma are un feature integrat foarte util: $metrics. Îți oferă date brute direct din Query Engine, inclusiv numărul exact de query-uri executate și timpul petrecut în DB pool.

Activezi opțiunea la inițializarea clientului și poți verifica numărul de interogări direct pe request-urile din mediul de stg/dev. Când vezi că prisma_client_queries_total crește liniar odată cu numărul de iteme din răspuns (ex: 1 item = 2 query-uri, 50 iteme = 51 query-uri), ai confirmat N+1-ul.

Soluția: include smart vs fetch manual

Soluția clasică este să folosești include. Cu include, Prisma știe de la început ce relații ai nevoie și își optimizează interogările în spate prin batching inteligent.

Dar atenție la trade-off: include fără restricții e un pericol la fel de mare. Dacă faci include: { comments: true } pe o listă lungă, vei trage în memorie toate câmpurile, inclusiv textul lung al comentariilor sau blobs. Am văzut servicii Node.js care intrau în OOM doar pentru că aduceau tot din DB în RAM.

Soluția curată e un select strict direct pe relație, sau extragerea ID-urilor și un findMany cu operatorul in. Prin trecerea de la .map() asincron la un include optimizat cu select, am scăzut încărcarea pe procesor de la 95% la 18% și am economisit 30% la memory footprint.

Voi cum monitorizați query-urile în staging? Vă bazați pe log-uri de SQL sau aveți teste de query count?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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