eduardweb.
Ajutor & ÎntrebăriAvansat#nodejs#prisma#postgresql#ajutor#aws

Mă bate o eroare Prisma în prod: 'Transaction already closed'. Ce naiba îmi scapă?

De Ioan Manole, 2 iul. 2026 · 17 vizualizări · 2 like-uri

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

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

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

  return await tx.order.create({
    data: { userId, total, status: 'PENDING' },
  });
}, {
  maxWait: 15000,
  timeout: 20000,
});

Salutare tuturor. Am dat de o belea pe care nu reușesc să o rezolv de vreo patru zile și am zis să cer o a doua opinie aici, poate s-a mai lovit cineva de zidul ăsta în producție.

Avem un SaaS de e-commerce destul de activ, cam 15.000 de utilizatori pe zi. Stack-ul e simplu: Next.js, Postgres pe AWS RDS (o instanță db.t4g.medium, destul de lejeră) și Prisma ca ORM. Totul a mers brici până acum o lună, când am început să vedem în Sentry eroarea asta: Transaction already closed.

Apare complet aleatoriu. Uneori de 5 ori pe zi, alteori de 20 de ori, de obicei în timpul orelor de vârf, când avem spike-uri de scriere pe tabela de comenzi. Clientul primește un 500 urât la checkout, deși banii uneori se încasează prin Stripe. E scenariul de coșmar pentru orice dev.

Ce am încercat deja

Am citit documentația Prisma de trei ori de la cap la coadă și am luat la rând toate soluțiile clasice propuse pe GitHub sau StackOverflow.

1. Am mărit timeout-urile la tranzacție

Implicit, o tranzacție interactivă în Prisma se închide după 5 secunde. Am zis că poate unele query-uri sunt mai lente sub încărcare mare. Am modificat codul și am pus valori mari, poate prea mari pentru prod, doar ca să elimin ipoteza asta. Am setat maxWait: 15000 (timpul de așteptare pentru a prinde o conexiune liberă din pool) și timeout: 20000 (timpul maxim de execuție al tranzacției).

Rezultatul? Nicio schimbare. Aceeași eroare apare uneori și după doar 2 secunde de la inițierea tranzacției, deci clar nu e un timeout real de 20 de secunde.

2. Tuning la Connection Pool și pgBouncer

Am bănuit că rămânem fără conexiuni libere în baza de date. Am urcat connection_limit la 30 în URL-ul de conexiune directă și am configurat și pgBouncer în față în modul session (fiindcă Prisma nu iubește transaction mode fără workaround-uri dubioase). În RDS, CPU-ul nu trece de 35%, iar conexiunile active sunt pe la jumătatea limitei maxime. Baza de date respiră ușurată, nu pare să fie de acolo.

3. Audit pe cod

Știu regula de aur: nu pui apeluri de API externe, trimiteri de email-uri sau criptări grele în interiorul $transaction. Am verificat tot fluxul de checkout. Tot ce facem în tranzacție sunt operațiuni pure de bază de date: citim stocul, creăm comanda, actualizăm stocul și salvăm log-ul de plată. Totul se execută în milisecunde în mod normal.

Unde ar mai putea fi problema?

Am început să suspectez chestii mai obscure de infrastructură sau runtime.

Poate fi vorba de Event Loop lag în Node.js? Dacă serverul e super ocupat cu alte request-uri concurente și întârzie să trimită următorul pas din tranzacție către baza de date, Prisma query engine (care e scris în Rust și rulează ca proces separat) s-ar putea să creadă că tranzacția a abandonat conexiunea și o închide forțat.

A doua teorie e legată de instabilitatea rețelei între serverul de aplicație (rulează în ECS Fargate) și RDS. Dacă pică conexiunea chiar și pentru 50ms în mijlocul tranzacției, Prisma probabil închide tot și aruncă eroarea asta generică în loc de un Connection lost mai util.

A mai trecut cineva prin coșmarul ăsta cu interactive transactions în Prisma? Cum ați debugat-o? Deja mă bate gândul serios să dau drop la Prisma pe partea de checkout și să scriu query-ul în SQL chior folosind Kysely, dar parcă n-aș vrea să am două abordări diferite în același proiect dacă nu e absolut necesar. 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.