eduardweb.
Ajutor & ÎntrebăriIntermediar#nodejs#prisma#postgresql#debugging#nestjs

Eroarea Prisma 'Transaction already closed' random în prod — am epuizat ideile

De George Iliescu, 6 aug. 2026 · 10 vizualizări · 2 like-uri

Postat 6 aug. 2026
typescript
return await this.prisma.$transaction(
  async (tx) => {
    const product = await tx.product.findUnique({
      where: { id: productId },
    });

    if (product.stock < quantity) {
      throw new BadRequestException('Stoc insuficient');
    }

    await tx.product.update({
      where: { id: productId },
      data: { stock: { decrement: quantity } },
    });

    // Crapă uneori direct aici cu 'Transaction already closed'
    return await tx.order.create({
      data: { productId, quantity, userId },
    });
  },
  {
    maxWait: 5000,
    timeout: 10000,
  }
);

Salutare tuturor. Am dat peste o problemă urâtă rău pe un microserviciu de checkout (Node.js 20 cu NestJS și Prisma 5.12) și sincer am epuizat toate variantele clasice de debugging.

Avem o eroare de Transaction already closed care apare complet random în producție. Se întâmplă cam de 40-50 de ori pe zi la un volum de 12.000 RPM, dar în staging (unde avem traficul sintetic generat cu k6) nu am reușit să o reproduc neam, oricât am tras de ea.

Cum arată codul și ce facem acolo

Scenariul e destul de standard: procesăm o comandă, scădem stocul pentru vreo 3-5 produse și salvăm un log de audit. Totul e împachetat într-o tranzacție interactivă Prisma (prisma.$transaction(async (tx) => { ... })).

Nivelul de procesare e destul de simplu, nu facem fețuiri de API-uri externe în interiorul tranzacției (știu regula de aur, nu blochezi DB-ul așteptând HTTP responses). Codul e foarte simplu, un findUnique, două update-uri și un create la final.

Ce am încercat deja și NU a rezolvat problema

  1. Am crescut timeout-ul explicit. Am pus timeout: 10000 și maxWait: 5000 în opțiunile tranzacției. Mă gândeam că poate e lag de rețea. Degeaba, eroarea apare la fel de des și uneori crapă după doar 200ms, nu după cele 10 secunde setate.

  2. Am verificat conexiunile de PostgreSQL și PgBouncer. Suntem pe RDS Postgres 15. Avem PgBouncer în fața bazei de date în session mode. CPU-ul pe DB e la sub 20%, iar numărul de conexiuni active e pe la 30 din maxim 200. Nicio urmă de query-uri blocate (pg_stat_activity e curat).

  3. Am scanat codul după await-uri lipsă. Am verificat de trei ori cu ESLint și la mână dacă nu cumva scapă vreun Promise neașteptat în interiorul callback-ului de tranzacție care să meargă în fundal și să încerce să folosească tx după ce callback-ul a dat return. Totul pare curat.

  4. Am făcut upgrade la Prisma. Eram pe v5.8.0, am urcat la 5.12.0 sperând să fie un bug rezolvat în engine-ul de Rust. Nicio diferență.

Suspiciunea mea: Event Loop Lag sau Rust Engine Issue?

Interactive transactions în Prisma funcționează puțin diferit față de un driver nativ cum e pg. Cât timp callback-ul tău rulează, Engine-ul de Rust ține conexiunea deschisă și pasează mesaje peste IPC/HTTP local cu procesul Node.js.

Bănuiala mea e că avem spike-uri scurte de Event Loop Lag pe pod-urile de Kubernetes. Dacă un Garbage Collection sau o altă operație CPU-intensive blochează Event Loop-ul Node.js pentru 2-3 secunde, Engine-ul de Rust crede că JS-ul a abandoned tranzacția și îi dă rollback client-side. Când Event Loop-ul își revine și execută următorul await tx.product.update(), Prisma aruncă Transaction already closed pentru că socket-ul intern e închis.

Trade-off-ul masiv la tranzacțiile interactive din Prisma e fix dependența asta strânsă de sănătatea Event Loop-ului JS din timpul executării. Dacă eram pe un SQL chior (BEGIN ... COMMIT), conexiunea rămânea pur și simplu în așteptare pe serverul de DB.

A mai lovit cineva zidul ăsta în prod la trafic mare? Cum ați izolat problema — ați trecut pe tranzacții secvențiale (prisma.$transaction([ ... ])) sau ați găsit vreun tweak la nivel de OS / Node flag-uri?

Orice idee e binevenită că deja îmi vine să rescriu tot modulul pe pg-promise.

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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