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

PgBouncer în producție: Configurare corectă pentru Prisma și modul transaction

De Sorin Tudor, 8 aug. 2026 · 7 vizualizări · 2 like-uri

Postat 8 aug. 2026
prisma
// schema.prisma
datasource db {
  provider  = "postgresql"
  url       = env("DATABASE_URL")      // PgBouncer port 6432 (?pgbouncer=true)
  directUrl = env("DIRECT_URL")        // Direct Postgres port 5432
}

// .env
// URL pentru traficul normal de aplicatie (Transaction mode)
DATABASE_URL="postgresql://postgres:pass@pgbouncer.internal:6432/mydb?pgbouncer=true&connection_limit=10"

// URL folosit doar de CLI-ul Prisma pentru migrate / db push
DIRECT_URL="postgresql://postgres:pass@db.internal:5432/mydb"

Prisma deschide conexiuni ca spartul dacă nu ești atent, iar în serverless sau arhitecturi cu multe pod-uri de Kubernetes ajungi rapid să atingi max_connections. Am pățit-o pe un proiect e-commerce la un spike de 8k useri simultani, când Postgres a început să refuze conexiuni noi și procesorul bazei de date era în 100% doar din context switching. Soluția rapidă a fost să punem PgBouncer în față, dar integrarea cu Prisma a venit cu câteva capcane serioase.

Session vs Transaction Mode: Unde te arzi

Implicit, mulți oameni lasă PgBouncer pe modul Session. În modul ăsta, PgBouncer funcționează ca un proxy transparent: atașează o conexiune fizică din Postgres unui client din aplicație și o ține blocată până când clientul se deconectează explicit. Dacă ai 200 de instanțe de Node.js care țin conexiuni deschise "just in case", ai rezolvat exact nimic.

Modul Transaction este singurul care face magie cu adevărat în producție. Aici, PgBouncer alocă o conexiune din Postgres doar pe durata unei tranzacții SQL (BEGIN -> COMMIT/ROLLBACK). Imediat ce tranzacția s-a terminat, conexiunea se întoarce în pool și poate fi folosită de alt request HTTP. Am redus conexiunile reale la Postgres de la 400 la doar 25, deservind același volum de trafic.

Trade-off-ul e că pierzi anumite facilități PostgreSQL: nu mai poți folosi LISTEN/NOTIFY, SQL-ul SET nu persistă între request-uri, iar prepared statements clasice crapă spectaculos dacă nu le gestionezi corect.

Cum dimensionezi pool-ul fără să ghicești

Ecuația clasică recomandată de echipa PostgreSQL pentru conexiuni directe este (CPU cores * 2) + disk count. Pentru o instanță RDS db.m6g.xlarge (4 vCPU, 16GB RAM), asta înseamnă cam 10-15 conexiuni active la nivel de Postgres.

În PgBouncer configurezi lucrurile pe două niveluri:

  1. La nivel de Postgres (postgresql.conf): Setezi max_connections = 100. Nu lăsa 1000, pentru că fiecare conexiune inactivă mănâncă stivă de RAM și overhead de OS.
  2. În pgbouncer.ini:
    • default_pool_size = 20 (conexiunile reale, permanente către Postgres)
    • reserve_pool_size = 5 (conexiuni de urgență când se umple coada)
    • max_client_conn = 2000 (câte conexiuni dinspre Node.js/Prisma acceptă PgBouncer în coadă)

Clienții stau la coadă în PgBouncer la nivel de socket TCP, ceea ce costă memorie neglijabilă comparativ cu un proces backend Postgres.

Cosmarul Prisma + Transaction Mode

Implicit, Prisma încearcă să folosească prepared statements (s_1, s_2) pentru performanță. În transaction mode, request-ul 1 trimite PREPARE s_1, iar request-ul 2 ajunge pe altă conexiune fizică și execută EXECUTE s_1, iar Postgres aruncă eroare: prepared statement "s_1" does not exist.

Ca să rezolvi asta în producție, ai nevoie de două URL-uri separate de conexiune. URL-ul principal trece prin PgBouncer (port 6432) cu parametrul ?pgbouncer=true care îi spune Prisma-ului să dezactiveze prepared statements. Al doilea URL (directUrl) se leagă direct la Postgres (port 5432) și este folosit EXCLUSIV pentru migrări (prisma migrate deploy), deoarece migrările au nevoie de lock-uri pe tabelă și tranzacții de lungă durată pe care PgBouncer le poate rupe.

Atât. Cu configurarea asta am dus un cluster de la crah-uri zilnice la 99.99% uptime și latență constantă sub 15ms pe query.

Voi cum gestionați pooling-ul pe proiecte mari? Ați mers pe soluții managed gen Supabase Transaction Pooler / AWS RDS Proxy sau rulați PgBouncer sidecar în Kubernetes?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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