// prisma/schema.prisma
datasource db {
provider = "postgresql"
// URL-ul prin PgBouncer (ex: port 6432) cu parametrul obligatoriu pgbouncer=true
url = env("DATABASE_URL")
// Conexiunea directă la Postgres (port 5432) folosită exclusiv pentru Prisma Migrate
directUrl = env("DIRECT_DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}Salutare tuturor. Anul trecut, pe o aplicație de e-commerce cu vreo 14.000 de useri activi la peak, ne-am trezit că baza de date Postgres crăpa frecvent din lipsă de RAM și conexiuni. Fiecare pod de Kubernetes rulat cu Prisma își deschidea propriul pool de conexiuni, ajungând rapid la peste 500 de procese postgres: user db active. Ieșirea din impas a fost instalarea PgBouncer, dar nu fără vreo două nopți pierdute cu erori bizar de greu de depanat.
Session vs Transaction mode – diferența care îți salvează serverul
Foarte mulți devs pun PgBouncer în față și îl lasă pe modul implicit, adică session. Nu faceți greșeala asta dacă aveți trafic mare.
În modul session, PgBouncer ține conexiunea fizică blocată către Postgres pe toată durata vieții conexiunii clientului. E util doar dacă ai aplicații vechi de PHP care deschid și închid conexiuni la fiecare request HTTP, dar pentru servicii moderne de Node.js sau Go e aproape degeaba.
Modul transaction e cel care face minuni. PgBouncer alocă o conexiune fizică din pool doar pe durata unei tranzacții SQL. Cum o interogare durează de obicei între 2 și 15 milisecunde, o singură conexiune Postgres reală poate deservi zeci sau sute de clienți concurenți.
Există un trade-off dur. În transaction mode pierzi opțiuni precum LISTEN/NOTIFY, SET LOCAL, advisory locks și, cel mai important, PREPARED STATEMENTS la nivel de sesiune. Dintr-o dată, driverele de Node.js încep să arunce erori de genul prepared statement "s_1" already exists sau does not exist.
Dimensiunea pool-ului: mai puțin înseamnă mult mai rapid
Prima tentație când vezi că ai mii de clienți e să pui default_pool_size = 300 în pgbouncer.ini. Așa am făcut și eu prima dată. Am fost complet neinspirat.
Postgres creează un proces OS separat pentru fiecare conexiune. La 300 de conexiuni active care încearcă să execute query-uri simultan, procesorul serverului își petrece 70% din timp făcând context switching între procese, nu executând instrucțiuni SQL.
Formula clasică recomandată pentru Postgres este: pool_size = (core_count * 2) + effective_spindle_count. Pentru un server de BD cu 8 vCPU și stocare NVMe, un default_pool_size stabilit între 20 și 30 de conexiuni oferă un throughput uriaș, mult mai mare decât cu 200 de conexiuni. Setăm max_client_conn = 5000 în PgBouncer, iar spre Postgres lăsăm doar 25 de țevi deschise. RAM-ul consumat pe DB a scăzut la noi de la 7GB la sub 1.5GB instant.
Cum configurezi Prisma corect
Prisma adoră să folosească prepared statements sub capotă pentru performanță. Când o pui după un PgBouncer în modul transaction, lucrurile o iau razna rapid dacă nu aplici două reguli simple în schema.prisma.
În primul rând, trebuie să adaugi parametrul ?pgbouncer=true la finalul URL-ului de conexiune din mediu. Asta îi transmite Prisma Client să folosească query-uri directe în loc de prepared statements.
În al doilea rând, Prisma Migrate are nevoie absolută de conexiune directă pentru că folosește advisory locks ca să se asigure că două migrări nu rulează în paralel. Soluția curată este să folosești câmpul directUrl în schema ta. Direct URL indică spre portul nativ de Postgres (5432), ocolind pool-ul de tranzacții la deploy.
Voi ce folosiți în producție pentru connection pooling? Păstrați PgBouncer-ul clasic sau ați trecut pe AWS RDS Proxy / Supabase Supavisor?