// schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL") // pgbouncer port 6432: postgres://user:pass@host:6432/db?pgbouncer=true
directUrl = env("DIRECT_URL") // direct postgres port 5432: postgres://user:pass@host:5432/db
}Am văzut prea multe baze de date PostgreSQL îngenuncheate de ORM-uri care deschid conexiuni ca și cum ar fi gratuite. Anul trecut, pe un cluster de K8s cu 12k useri simultani, microserviciile noastre cu Prisma au adus un Postgres de 16 vCPU la 100% CPU doar din overhead-ul conexiunilor TCP. Soluția a fost PgBouncer, dar integrarea cu Prisma vine cu câteva capcane serioase dacă nu înțelegi diferența dintre modurile de pooling.
De ce crapă Postgres când pui Prisma fără proxy
PostgreSQL folosește un model bazat pe procese (fork() pentru fiecare client). Fiecare conexiune nouă consumă între 2 MB și 10 MB de RAM doar ca overhead, dar problema reală e context switching-ul la nivel de CPU când ai sute de conexiuni concurente.
Prisma creează câte un pool intern pe fiecare instanță de aplicație. Dacă ai 20 de pod-uri de Node.js și fiecare deschide un pool de 10 conexiuni, ai deja 200 de conexiuni rezervate în Postgres, din care 90% stau degeaba waiting for I/O.
Session vs Transaction Mode: Unde se rupe filmul
PgBouncer are două moduri relevante în producție:
- Session mode: Conexiunea de la client e atașată de o conexiune Postgres pe toată durata sesiunii. Este cel mai sigur mod, suportă
LISTEN/NOTIFYși prepared statements nativ, dar e aproape inutil dacă vrei să reduci masiv numărul de conexiuni reale. - Transaction mode: PgBouncer atribuie o conexiune Postgres doar pe durata unei tranzacții (
BEGIN...COMMIT). Când query-ul s-a terminat, conexiunea fizică e eliberată imediat pentru alt client.
Trade-off-ul la Transaction mode? Pierzi suportul nativ pentru LISTEN/NOTIFY, tabele temporare pe sesiune și opțiunea SET la nivel de sesiune. Cu toate astea, când am trecut de la Session la Transaction mode, am scăzut conexiunile active pe Postgres de la 450 la doar 25, iar latența p95 a scăzut cu 35%.
Cum calculezi dimensiunea pool-ului
Formula clasică recomandată de echipa Postgres este:
max_connections = (CPU_cores * 2) + effective_spindle_count
Pentru un server cu 8 vCPU și stocare NVMe SSD, asta înseamnă undeva la 20-25 de conexiuni reale în Postgres. Da, ai citit bine. Să pui max_connections = 1000 în postgresql.conf e o rețetă sigură pentru dezastru sub load.
În PgBouncer configurezi:
default_pool_size = 20(conexiunile efective către Postgres)reserve_pool_size = 5max_client_conn = 1000(ceea ce pot deschide pod-urile tale în total)
Trik-ul specific pentru Prisma
Ca să meargă Prisma cu PgBouncer în pool_mode = transaction, trebuie neapărat să dezactivezi prepared statements adăugând ?pgbouncer=true în URL-ul de conexiune și să folosești un directUrl separat pentru migrări.
Dacă nu pui directUrl, comanda prisma migrate va eșua mizerabil în transaction mode deoarece DDL-urile au nevoie de conexiune directă sau session mode.
Voi ce folosiți în producție: PgBouncer, Supavisor sau proxy-ul nativ furnizat de providerul de cloud?