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

PgBouncer în producție: Cum am scalat Prisma fără să sufocăm Postgres

De Delia Petre, 7 aug. 2026 · 4 vizualizări · 3 like-uri

Postat 7 aug. 2026
prisma
// schema.prisma
datasource db {
  provider  = "postgresql"
  // URL-ul prin PgBouncer (Transaction Mode, port 6432)
  url       = env("DATABASE_URL")
  // Conexiunea directă la Postgres (Port 5432) folosită EXCLUSIV pentru migrații
  directUrl = env("DIRECT_URL")
}

// .env
// pgbouncer=true dezactivează prepared statements pe Prisma engine
DATABASE_URL="postgres://user:pass@pgbouncer-host:6432/dbname?pgbouncer=true&connection_limit=10"
DIRECT_URL="postgres://user:pass@postgres-host:5432/dbname"

Am ars-o urât cu Prisma și Postgres acum un an, când am scalat la 15 pod-uri de Node.js în Kubernetes. Baza de date își dădea duhul din cauza celor 350+ conexiuni active, iar RAM-ul pe serverul de DB ajunsese la 85%. Trecerea la PgBouncer ne-a scăzut consumul de memorie pe Postgres cu 60% și a tăiat latența medie a query-urilor de la 45ms la 12ms.

Session vs Transaction Mode: Unde se rupe filmul

Multă lume instalează PgBouncer și îl lasă pe modul session. Dacă faci asta, ai făcut un simplu proxy și n-ai rezolvat mai nimic. În modul session, o conexiune deschisă de client ține blocată conexiunea din Postgres până când clientul se deconectează. Dacă ai 100 de instanțe de Node, tot 100 de conexiuni reale ai în DB.

Magia adevărată e modul transaction. Aici, PgBouncer împrumută o conexiune reală din Postgres doar pe durata unei tranzacții (BEGIN ... COMMIT). Imediat ce s-a terminat query-ul sau tranzacția, conexiunea e redată pool-ului. În felul ăsta, 500 de clienți concurenți pot fi deserviți lejer de doar 20-30 de conexiuni reale în Postgres.

Trade-off-ul? Pierzi tot ce ține de stare la nivel de sesiune: LISTEN/NOTIFY, SET LOCAL făcute în afara tranzacțiilor, tabele temporare și, cel mai dureros pentru Node.js, prepared statements implicite.

Capcana Prisma: Prepared statements și erorile fantastice

Prisma încearcă să fie deștept și folosește prepared statements prin engine-ul lui în Rust. În transaction mode, query #1 ajunge pe conexiunea A de Postgres și creează prepared statement-ul s1. Query #2 trimis de același client ajunge prin PgBouncer pe conexiunea B din Postgres, unde s1 nu există. Rezultat: prepared statement "s1" does not exist sau crash-uri aleatorii în producție.

Ca să rezolvi asta, trebuie neapărat să folosești opțiunea directUrl pentru migrații și să adaugi ?pgbouncer=true pe URL-ul principal de pooling. Parametrul ăsta îi transmite Prisma Engine-ului să nu mai folosească prepared statements.

Niciodată să nu rulezi prisma migrate deploy prin portul de transaction pooling (6432 de obicei). Migrațiile au nevoie de adroitor de conexiune directă (port 5432) sau session mode, altfel se vor bloca în lock-uri pe DDL sau vor eșua lamentabil.

Cum calculezi Pool Size fără să dărâmi baza

Găsești des sfatul greșit: "pune default_pool_size = 100 în pgbouncer.ini ca să fie loc". Fals. Postgres nu e proiectat pentru mii de conexiuni active concurente — fiecare backend process consumă RAM (câțiva MB buni) și creează context switching grav pe CPU.

O regulă empirică pe care am testat-o la un volum de 8k request-uri/secundă pe un Postgres cu 8 vCPU și 32GB RAM:

  • În Postgres (postgresql.conf): max_connections = 100
  • În PgBouncer (pgbouncer.ini): default_pool_size = 25, reserve_pool_size = 5
  • În Prisma Connection String: connection_limit=10 (sau chiar mai mic per pod)

PgBouncer va ține doar 25-30 de conexiuni deschise spre Postgres, dar va putea accepta 2000+ de conexiuni de la aplicații în queue. Latența scade masiv fiindcă CPU-ul de pe DB nu mai procesează thread management, ci execută query-uri brute.

Voi ce folosiți în producție pentru pooling? Ați rămas pe clasicul PgBouncer sau ați migrat spre PgCat / Supavisor?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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