eduardweb.
Ajutor & ÎntrebăriIntermediar#nodejs#prisma#backend#postgres#ajutor

Mă bate o eroare Prisma în prod: 'Transaction already closed'. Voi cum ați rezolvat-o?

De Ioana Marinescu, 5 iun. 2026 · 2 vizualizări · 3 like-uri

Postat acum 4 zile
typescript
await prisma.$transaction(async (tx) => {
  const stock = await tx.product.findUnique({
    where: { id: productId },
  });

  if (!stock || stock.quantity < quantity) {
    throw new Error("Stoc insuficient");
  }

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

  return tx.order.create({
    data: { userId, total },
  });
}, {
  maxWait: 5000,
  timeout: 15000
});

Salutare tuturor. Scriu postarea asta pentru că sunt oficial în pană de idei și am ochii cârpiți de somn după trei nopți de debugging în producție. Am nevoie de o minte limpede care s-a mai lovit de comportamentul ăsta bizar.

Avemos un serviciu de checkout pe un proiect cu vreo 12.000 de utilizatori activi zilnic. Stack-ul e Next.js (Pages router, rulat în Docker pe ECS), Postgres pe RDS (un db.m6g.large, deci destulă resursă) și Prisma ca ORM. Totul rulează brici, mai puțin când avem vârfuri de trafic și începe să ne bombardeze Sentry cu: PrismaClientKnownRequestError: Transaction already closed: Transaction is no longer usable....

Cum arată codul buclucaș

Folosim tranzacții interactive pentru că trebuie să verificăm stocul, să blocăm produsele în baza de date și să creăm comanda. E un flux clasic de e-commerce unde nu vrei race conditions sau double-spend. Codul e destul de standard, nimic exotic, doar că e închis într-un bloc $transaction cu un mic timeout mărit preventiv.

Ce am încercat deja (și a eșuat)

1. Mărirea timeout-ului: Inițial am crezut că e un simplu timeout de rețea sau DB. Am crescut timeout-ul tranzacției de la default-ul de 5 secunde la 15 secunde (timeout: 15000, maxWait: 5000). Rezultatul? Eroarea tot apare, doar că acum utilizatorii așteaptă mai mult până crapă request-ul. Nu e de acolo.

2. Connection Pooling (PgBouncer): Am bănuit că rămânem fără conexiuni în pool. Am configurat PgBouncer în mod transaction și am adăugat ?pgbouncer=true în connection string, plus ajustat connection_limit la 20. Nimic schimbat, ba chiar parcă am văzut niște erori noi de protocol din cauza modului în care Prisma își pregătește query-urile (prepared statements). Am revenit rapid la conexiune directă către RDS pentru că oricum nu săream de 40 de conexiuni active.

3. Analiza query-urilor: Am pornit query logging-ul în staging și am simulat load cu k6. Tranzacția durează în medie 120ms. Nu avem query-uri blocate sau lock-uri masive pe tabele care să țină baza de date ocupată secunde în șir.

Trade-off-ul dubios

Am observat că dacă renunțăm complet la $transaction și facem update-urile "pe încredere" (cu un fel de optimistic locking pe versiunea rândului), eroarea dispare complet. Dar asta ne complică enorm logica de rollback dacă pică al treilea query din serie. Parcă nu aș vrea să rescriu tot fluxul cu rollback manual în JS doar pentru că ORM-ul decide să închidă conexiunea de capul lui.

Am citit pe GitHub issues că ar putea fi o problemă legată de Node.js event loop block. Prisma rulează un Query Engine în Rust ca proces separat, iar comunicarea se face prin HTTP/TCP local. Dacă event loop-ul din Node e blocat chiar și pentru 100ms din cauza altor operațiuni CPU-intensive, Prisma pierde conexiunea cu engine-ul și marchează tranzacția ca fiind închisă în mod eronat. Pe graficele de monitorizare din AWS, CPU-ul pe instanțele de Node nu trece de 40%, dar spikes scurte de milisecunde nu prea apar în CloudWatch.

A mai lovit cineva problema asta dubioasă în producție? Există vreo setare ascunsă de keep-alive sau pur și simplu Prisma nu e stabilă pe tranzacții lungi sub load? Orice idee e binevenită.

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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