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

PgBouncer în producție: Transaction vs Session mode, sizing corect și integrarea cu Prisma

De Paul Ene, 9 aug. 2026 · 4 vizualizări · 3 like-uri

Postat 9 aug. 2026
prisma
// schema.prisma
datasource db {
  provider  = "postgresql"
  url       = env("DATABASE_URL") // Port 6432 (PgBouncer)
  directUrl = env("DIRECT_URL")   // Port 5432 (Postgres direct pentru `prisma migrate`)
}

// .env
// pgbouncer=true dezactivează prepared statements la nivel de driver
// connection_limit=10 limitează conexiunile deschise de instanța curentă de Prisma către PgBouncer
DATABASE_URL="postgresql://user:pass@pgbouncer-host:6432/dbname?pgbouncer=true&connection_limit=10"
DIRECT_URL="postgresql://user:pass@postgres-host:5432/dbname"

M-am lovit de limita de conexiuni în Postgres pe un cluster cu 8 microservicii când am migrat un API vechi pe Prisma. Postgres începuse să mănânce peste 4GB RAM doar pe overhead-ul de conexiuni idle, pentru că fiecare conexiune nouă în Postgres e un proces separat în OS. Soluția evidentă a fost să pun PgBouncer în față, dar diavolul stă în detalii când încerci să faci pool_mode = transaction să meargă cu un ORM modern.

Session vs Transaction: unde se rupe filmul

În session mode, PgBouncer alocă o conexiune fizică de Postgres unui client și o ține blocată până când clientul se deconectează explicit. Practic e un proxy lejer. E util dacă folosești SET LOCAL, temp tables sau LISTEN/NOTIFY, dar salvează foarte puțin RAM dacă serviciile tale țin conexiunile deschise persistent.

Magia reală se întâmplă în transaction mode. Imediat ce o interogare sau o tranzacție SQL s-a terminat, PgBouncer redă conexiunea fizică înapoi în pool pentru alt request. Am scăzut conexiunile active pe instanța primară de la 450 la doar 25, fără nicio eroare de timeout pe clienți. Totuși, apare un trade-off dureros: pierzi suportul pentru prepared statements la nivel de sesiune. Dacă aplicația încearcă să refolosească un prepared statement pe o conexiune care a fost reatribuită altui client, Postgres aruncă erori de tipul prepared statement "S_1" does not exist.

Cum împaci Prisma cu PgBouncer

Prisma folosește implicit prepared statements și tinde să deschidă conexiuni agresiv. Ca să o faci să funcționeze corect pe transaction mode, trebuie să specifici două URL-uri distincte în configurare: unul prin PgBouncer pentru traficul aplicației și unul direct către Postgres pentru migrări.

În string-ul de conexiune principal trebuie neapărat adăugat parametrul ?pgbouncer=true. Acesta îi spune Query Engine-ului din Rust să nu mai optimizeze prin prepared statements persistente. De asemenea, atenție mare la tranzacțiile interactive (prisma.$transaction(async (tx) => ...)). Ele blochează o conexiune pe toată durata execuției callback-ului. Dacă ai pool-ul prea mic în PgBouncer și multe tranzacții lungi în paralel, intri rapid în deadlock.

Cum calculezi pool size fără să crape baza

O greșeală frecventă pe care o văd este setarea unor pool-uri uriașe în PgBouncer. Devii tentați să pună default_pool_size = 50 pe fiecare microserviciu, iar când scalezi la 10 pod-uri în Kubernetes, dintr-o dată depășești max_connections din postgresql.conf.

Formula clasică oferită de echipa Postgres pentru dimensiunea pool-ului fizic este: conexiuni = (core_count * 2) + effective_spindle_count

Pentru un server de DB cu 8 vCPU și stocare NVMe, un număr de 20-30 de conexiuni fizice active oferă un throughput mult mai bun decât 500 de conexiuni concurente. Pui default_pool_size = 25 în PgBouncer și crești max_client_conn la 2000. Astfel, mii de request-uri HTTP așteaptă câteva milisecunde la ușa PgBouncer, dar baza lucrează liniștită fără context switching masiv între procese.

Voi ce setup folosiți când aveți servicii multe care lovesc aceeași bază? Ați trecut pe alternative gen Supavisor / PgCat sau tot PgBouncer clasic e baza?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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