Il tuo backend va in timeout, le query diventano lente, e nei log compare un errore che conosci fin troppo bene: "sorry, too many clients already". Il sito non è caduto per un problema di codice. È caduto perché PostgreSQL ha un limite di connessioni, e la tua applicazione lo ha superato.
Noi, di Meteora Web, lo vediamo spesso nei progetti che ci arrivano in consulenza. Un e-commerce che va in tilt durante i saldi, un gestionale che si blocca a fine mese, un'app che muore quando parte la campagna ADS. Il colpevole è quasi sempre lo stesso: connessioni al database non gestite.
In questa guida ti mostriamo come risolvere il problema alla radice con PgBouncer, il connection pooler più usato in produzione. Non ti diamo solo la configurazione: ti spieghiamo perché funziona, come dimensionarla, e come evitare gli errori che fanno danni peggiori del problema iniziale.
Perché PostgreSQL va in crash con troppe connessioni e come funziona il connection pooling
PostgreSQL è un processo per connessione. Ogni client che si collega fa partire un processo server che consuma memoria e CPU. Con 100 connessioni attive, hai 100 processi. Con 1000, ne hai 1000. Il sistema operativo prima o poi dice basta.
Il connection pooling risolve il problema con un'intermediazione: le applicazioni non si collegano direttamente a PostgreSQL, ma a un pooler che tiene aperte un numero fisso di connessioni reali. Quando la tua app chiede una connessione, il pooler gliene presta una già aperta. Quando ha finito, la restituisce.
Il risultato è che puoi avere 500 applicazioni client che usano solo 20 connessioni reali al database. La differenza tra un server che regge e uno che va in crash.
Il problema di default: max_connections e la memoria
Il parametro max_connections di default è 100. Ogni connessione inattiva consuma comunque memoria. Se ogni processo usa in media 10 MB, 100 connessioni sono 1 GB di RAM solo per tenere aperte le porte.
Sponsored Protocol
Alzare max_connections a 500 non è la soluzione: è il modo più veloce per saturare la RAM e far finire il server in swap. Il pooling non è un'opzione, è una necessità.
-- Verifica quante connessioni sono attive adesso
SELECT count(*) FROM pg_stat_activity;
-- Verifica il limite attuale
SHOW max_connections;
-- Controlla la memoria usata per processo (in Linux)
ps aux | grep postgres | awk '{sum+=$6} END {print sum/1024 " MB"}'
Checklist operativa: controlla ora quante connessioni hai attive e quanto consuma PostgreSQL. Se il numero di connessioni è vicino al limite, sei già in zona pericolo.
Come si installa PgBouncer e quale modalità scegliere per la tua applicazione
PgBouncer è leggero, configurabile in pochi minuti, e funziona con qualsiasi applicazione che parli il protocollo PostgreSQL. Si installa con un semplice comando e si configura con un file di testo.
# Installazione su Ubuntu/Debian
sudo apt update && sudo apt install pgbouncer
# Installazione su CentOS/RHEL
sudo dnf install pgbouncer
La scelta più importante è la modalità di pooling. PgBouncer ne offre tre, e sbagliare questa scelta significa avere problemi di compatibilità o performance scadenti.
Session pooling: la modalità predefinita e più sicura
In modalità session, la connessione viene assegnata a un client per tutta la durata della sua sessione. È la più semplice e compatibile: funziona con qualsiasi applicazione, anche quelle che usano funzioni avanzate come le temporary table o le session-level SET.
Il limite è che il numero di connessioni reali deve essere almeno pari al numero di client simultanei. Se hai 200 utenti attivi, ti servono 200 connessioni al database. Il pooler non riduce il numero di connessioni, ma le gestisce in modo più efficiente.
Transaction pooling: la modalità che salva il server
In modalità transaction, la connessione viene assegnata solo per la durata di una transazione. Quando la transazione finisce, la connessione torna nel pool e può essere usata da un altro client.
Sponsored Protocol
Qui il risparmio è enorme: con 20 connessioni reali puoi servire centinaia di client simultanei. Ma ci sono vincoli: niente temporary table, niente session-level SET, e l'applicazione deve gestire correttamente il commit/rollback. Se usi un ORM moderno come Laravel Eloquent o Django ORM, di solito funziona senza problemi.
# /etc/pgbouncer/pgbouncer.ini
[databases]
myapp = host=127.0.0.1 port=5432 dbname=myapp
[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20
Decisione da prendere: se la tua applicazione usa temporary table o SET a livello di sessione, scegli session pooling. Se usi un ORM standard, scegli transaction pooling e riduci le connessioni reali del 90%.
Come si configurano gli utenti e le password in PgBouncer per non andare in errore
PgBouncer non legge direttamente da PostgreSQL: ha un file userlist.txt con le credenziali. Questo file va aggiornato quando cambi le password degli utenti, ed è una delle fonti di errore più comuni.
La password nel file deve essere in formato MD5, non in chiaro. Puoi generarla con un comando semplice.
# Genera la password MD5 per l'utente myuser con password 'mypassword'
echo -n "mypassword" | openssl dgst -md5 | awk '{print "md5" $2}'
# Aggiungi al file /etc/pgbouncer/userlist.txt
# "myuser" "md5generata"
Un errore comune è dimenticare di aggiornare questo file dopo un cambio password sul database. Il risultato è un errore di autenticazione che confonde perché la password sul database è corretta.
# Test rapido di connessione a PgBouncer
psql -h 127.0.0.1 -p 6432 -U myuser -d myapp
# Se funziona, sei operativo. Se no, controlla i log
sudo tail -f /var/log/postgresql/pgbouncer.log
Azione immediata: crea il file userlist.txt con le credenziali dei tuoi utenti e testa la connessione. Se il test fallisce, guarda i log: il 90% delle volte è un problema di password non sincronizzata.
Sponsored Protocol
Come si dimensiona default_pool_size e max_client_conn per il tuo carico di lavoro
Il dimensionamento è la parte che separa chi usa PgBouncer da chi lo usa bene. Sbagliare i numeri significa o non risolvere il problema, o crearne di nuovi.
La regola d'oro: non superare il numero di core della CPU per il pool di connessioni reali. PostgreSQL non diventa più veloce con più connessioni: oltre un certo limite, il context switching della CPU rallenta tutto.
La formula pratica per il dimensionamento
Per un server con 4 core e 16 GB di RAM, la configurazione tipica è:
default_pool_size = 20
max_client_conn = 1000
20 connessioni reali per 4 core sono 5 per core: un buon punto di partenza. Se hai query complesse che usano molto la CPU, scendi a 3-4 per core. Se hai query leggere, puoi salire a 6-8.
Il max_client_conn è il limite di connessioni che PgBouncer accetta. Non serve metterlo a 10000: se la tua applicazione ha bisogno di più di 1000 client simultanei, hai un problema di architettura, non di configurazione.
Calcolo da fare subito: verifica quanti core ha il tuo server con nproc, moltiplica per 5, e usa quel numero come default_pool_size. Poi monitora per una settimana e aggiusta.
Come si monitora PgBouncer con SHOW POOLS e si risolvono i problemi più comuni
PgBouncer ha un'interfaccia di amministrazione integrata. Collegandoti al database virtuale pgbouncer, puoi vedere lo stato del pool in tempo reale.
# Collegati all'interfaccia di amministrazione
psql -h 127.0.0.1 -p 6432 -U pgbouncer -d pgbouncer
# Mostra lo stato dei pool
SHOW POOLS;
# Mostra le statistiche delle query
SHOW STATS;
La colonna cl_active ti dice quante connessioni sono in uso. Se vedi che cl_active è sempre vicino a cl_waiting alto, significa che il pool è troppo piccolo e le applicazioni stanno aspettando. Se cl_waiting cresce, aumenti default_pool_size.
Sponsored Protocol
Errore comune: il pool è troppo piccolo e le query vanno in timeout
Se le tue applicazioni vanno in timeout ma PostgreSQL non è sotto stress, il problema è che il pool è saturo. Le query aspettano una connessione libera invece di eseguirsi. La soluzione non è aumentare le connessioni, ma ridurre il tempo di ogni transazione.
Un altro errore comune è dimenticare di configurare server_idle_timeout. Questo parametro chiude le connessioni reali che restano inattive troppo a lungo, liberando memoria.
server_idle_timeout = 300
server_lifetime = 3600
Checklist di controllo: collega a pgbouncer, esegui SHOW POOLS, e controlla se cl_waiting è maggiore di 0. Se lo è, hai un collo di bottiglia. Se cl_active è sempre al massimo, riduci il tempo delle transazioni o aumenta il pool.
Come si integra PgBouncer con Laravel, Django e altre applicazioni senza rompere nulla
L'integrazione con le applicazioni è quasi trasparente: basta cambiare host e porta nel file di configurazione. Ma ci sono dettagli che fanno la differenza tra un sistema stabile e uno che si rompe a caso.
Laravel e PHP: attenzione alla durata delle connessioni
Laravel usa una connessione per richiesta. Con PgBouncer in modalità transaction, funziona bene. Ma se hai code che elaborano job in background, assicurati che ogni job chiuda la connessione quando ha finito.
// config/database.php
'pgsql' => [
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '6432'), // PgBouncer port
'database' => env('DB_DATABASE', 'myapp'),
'username' => env('DB_USERNAME', 'myuser'),
'password' => env('DB_PASSWORD', 'mypassword'),
],
Django e Python: gestire le connessioni persistenti
Django per default tiene le connessioni aperte per il riuso. Con PgBouncer in modalità transaction, questo è il comportamento ideale. Ma se usi CONN_MAX_AGE, assicurati che il valore non superi server_lifetime di PgBouncer.
Sponsored Protocol
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'HOST': '127.0.0.1',
'PORT': '6432',
'CONN_MAX_AGE': 300, # Deve essere minore di server_lifetime
}
}
Errore da evitare: se la tua applicazione usa LISTEN/NOTIFY o pg_advisory_lock, la modalità transaction pooling non funziona. Queste funzioni richiedono la sessione, quindi devi usare session pooling o dedicare un database separato per queste operazioni.
Passo operativo: cambia la porta nel tuo ORM da 5432 a 6432 e testa. Se tutto funziona, sei operativo. Se hai errori con temporary table o SET, passa a session pooling.
Cosa fare adesso
Hai gli strumenti per risolvere il problema delle connessioni. Non serve altro. Ecco i passi da fare subito:
1. Verifica il problema. Esegui SELECT count(*) FROM pg_stat_activity e controlla se sei vicino al limite.
2. Installa PgBouncer. Configura il file pgbouncer.ini con la modalità giusta per la tua applicazione.
3. Crea il file userlist.txt. Genera le password MD5 e testa la connessione.
4. Cambia la porta nella tua applicazione. Da 5432 a 6432, e testa in staging prima di andare in produzione.
5. Monitora per una settimana. Usa SHOW POOLS e aggiusta default_pool_size se vedi code in attesa.
Se ti serve una mano, noi di Meteora Web lo facciamo ogni giorno. Non solo configuriamo PgBouncer: dimensioniamo il pool, ottimizziamo le query e rendiamo il database un punto di forza, non un collo di bottiglia. Il tuo server merita di reggere il carico, e il tuo business merita di non fermarsi per un errore di configurazione.