// .env
// Conexiunea prin PgBouncer (port 6543) pentru interogări rapide în producție
DATABASE_URL="postgresql://user:password@localhost:6543/mydb?pgbouncer=true&connection_limit=3"
// Conexiunea directă (port 5432) folosită doar de Prisma pentru migrări
DIRECT_DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
// schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_DATABASE_URL")
}Salutare. Dacă folosești Prisma cu PostgreSQL în producție și ai trecut de faza de MVP, sigur te-ai lovit de faimoasa eroare "too many clients already". Hai să vorbim direct despre cum configurezi PgBouncer ca să nu-ți crape baza de date când ai spike-uri de trafic.
Am pățit-o la un proiect cu vreo 12.000 de utilizatori activi. Fără un pooler, Node.js deschidea conexiuni verzi-uscate până când baza de date (un RDS destul de măricel) începea să facă throttling din cauza consumului de RAM și context switching. Soluția a fost PgBouncer, dar integrarea cu Prisma are câteva chichițe destul de enervante.
Dilema: Session vs Transaction mode
Aici se decide totul. PgBouncer poate rula în mai multe moduri, dar în producție te loviești de astea două:
Session mode: PgBouncer îi oferă aplicației o conexiune și o lasă deschisă până când aplicația o închide explicit. Este fix ce face și Postgres nativ. Pentru serverless sau containere care scalează dinamic, modul ăsta e aproape inutil. Nu rezolvă problema numărului mare de conexiuni concurente.
Transaction mode: Aici e magia. Conexiunea fizică la Postgres este ținută doar pe durata unei tranzacții SQL. Imediat ce interogarea s-a terminat, conexiunea se întoarce în pool pentru a fi folosită de alt request.
Dar există un trade-off major în transaction mode. Pierzi suportul pentru prepared statements (pentru că PgBouncer poate trimite următorul query din același script pe o altă conexiune fizică unde statement-ul nu e pregătit). Din fericire, Prisma știe să gestioneze asta dacă îi spunem noi cum.
Configurația corectă pentru Prisma
Prisma are nevoie de două conexiuni diferite în fișierul schema.prisma. De ce? Pentru că migrările (prisma migrate) au nevoie de comenzi DDL care blochează tabele și rulează într-o singură sesiune lungă. Dacă încerci să rulezi migrări prin PgBouncer în mod transaction, procesul o să crape spectaculos.
Trebuie să expui două URL-uri:
url- care indică spre PgBouncer (portul standard e adesea 6543) cu parametrulpgbouncer=true.directUrl- care se conectează direct la baza de date (portul 5432) și este folosit exclusiv pentru migrări și introspecție.
Cum calculăm Pool Size?
Nu pune numere din burtă. Am văzut echipe care puneau pool_size = 100 în PgBouncer pe o mașină cu 2 vCPUs. Total greșit. Fiecare conexiune activă în Postgres consumă memorie și CPU pentru managementul procesului.
Formula empirică pe care o folosesc și care ne-a redus CPU-ul bazei de date cu 30% este:
conexiuni_maxime = ((număr_procesoare * 2) + număr_discuri)
Pentru un server cu 4 vCPUs și SSD-uri rapide, asta înseamnă cam 10 conexiuni active. În pgbouncer.ini, setează default_pool_size = 12 (lăsăm o mică marjă).
În aplicația Node.js, în connection string-ul pentru PgBouncer, setează connection_limit=3. Dacă ai 4 instanțe de Node, ele vor cere în total maximum 12 conexiuni, potrivindu-se perfect cu pool-ul din PgBouncer. Restul de request-uri peste limită vor aștepta câteva milisecunde în coada PgBouncer, ceea ce e infinit mai bine decât să moară baza de date.
Voi cum gestionați conexiunile când scalați? Mergeți pe PgBouncer clasic sau ați trecut deja pe soluții serverless gen Neon sau AWS RDS Proxy?