eduardweb.
PostgreSQLAvansat#devops#prisma#postgresql#database#pgbouncer

PgBouncer în producție: Cum împaci modul Transaction cu Prisma fără să-ți crape baza de date

De George Iliescu, 28 mai 2026 · 5 vizualizări · 2 like-uri

Postat 28 mai 2026
typescript
// schema.prisma
datasource db {
  provider  = "postgresql"
  // URL-ul principal trece prin PgBouncer pe portul 6432 (transaction mode)
  // Adăugăm ?pgbouncer=true pentru a opri prepared statements
  url       = env("DATABASE_URL_PGBOUNCER") 
  
  // directUrl se conectează direct la Postgres pe portul 5432
  // Este folosit doar pentru migrări (prisma migrate deploy)
  directUrl = env("DATABASE_URL_DIRECT")
}

Am dat cu capul de pragul de sus acum vreo doi ani, pe un proiect unde am crescut destul de repede la peste 15.000 de utilizatori activi concurenți. Serverul de Postgres (un RDS destul de măricel) începuse să gâfâie din cauza conexiunilor deschise de instanțele noastre de Node.js din ECS. Prisma, oricât de mult îmi place ca DX, deschidea conexiuni ca spartul pentru fiecare container pornit de autoscaling.

Atunci am decis să punem PgBouncer în față. Sună simplu pe hârtie, dar dacă folosești Prisma, trecerea la pooling în producție vine cu niște capcane destul de mari de care m-am lovit direct.

Dilema: Session vs Transaction Mode

Aici se decide totul. PgBouncer are trei moduri, dar în producție discuția se poartă doar între session și transaction.

Modul Session e cel mai sigur, dar e aproape degeaba dacă vrei scalabilitate masivă. Când un container Node cere o conexiune, PgBouncer îi alocă una din Postgres și o lasă blocată acolo până când containerul moare sau închide explicit conexiunea. Dacă ai 50 de instanțe de server care țin conexiunile deschise degeaba, ai rezolvat exact nimic.

Modul Transaction e sfântul graal. PgBouncer împrumută o conexiune fizică din Postgres doar pe durata unei singure tranzacții SQL. Imediat ce query-ul s-a executat, conexiunea se întoarce în pool pentru alt client.

Trade-off-ul sincer? În mod transaction pierzi funcționalități importante. Uiți de PREPARE (prepared statements), de tabele temporare (TEMP TABLE) și de anumite funcții de locking la nivel de sesiune. Dacă aplicația ta se bazează pe ele, ești mâncat.

Coșmarul Prisma și prepared statements

Prisma folosește implicit prepared statements pentru optimizarea performanței. Într-un pool în mod transaction, query-ul tău de PREPARE ajunge pe conexiunea fizică A, iar când Prisma vrea să ruleze EXECUTE, PgBouncer s-ar putea să o trimită pe conexiunea fizică B. Rezultatul? Erori criptice de tipul prepared statement "s0" does not exist și crash-uri în producție.

Ca să rezolvăm asta, am fost nevoiți să facem un setup cu două conexiuni diferite în schema de Prisma:

  1. Conexiunea de query-uri (prin PgBouncer în mod transaction), unde forțăm dezactivarea prepared statements prin adăugarea parametrului ?pgbouncer=true în URL.
  2. Conexiunea de migrări (directă sau printr-un port de PgBouncer în mod session). Migrările Prisma au nevoie de acces exclusiv și de blocări pe tabele pe care modul transaction le-ar bloca complet.

Cum am calculat Pool Size în viața reală

Nu lăsați setările default din tutoriale. Dacă ai max_connections = 200 setat în Postgres, nu pune pool_size = 200 în PgBouncer. Ai nevoie de o zonă de siguranță pentru conexiuni directe (cron job-uri, scripturi de urgență, admini care fac debug).

La proiectul nostru, formula care a stabilizat sistemul a fost:

  • În Postgres: max_connections = 250
  • În PgBouncer: default_pool_size = 150 și max_client_conn = 10000

Practic, am permis la peste 8.000 de clienți concurenți din Node să se bată pe doar 150 de conexiuni reale către Postgres. Utilizarea CPU pe baza de date a scăzut instant de la 85% la sub 25%, iar latența generală a aplicației s-a îmbunătățit pentru că Postgres nu mai pierdea timp cu handshake-uri de TCP și alocare de memorie pentru conexiuni noi.

În concluzie, PgBouncer în mod transaction este obligatoriu dacă folosești un ORM modern pe serverless sau microservicii care scalează agresiv. Trebuie doar să accepți că migrările se fac pe alt canal și că pierzi optimizarea de prepared statements din Prisma.

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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