Django persistent connections with PgBouncer?
We have database performance issues and I am looking into adding PgBouncer to handle connections more efficiently and hopefully solve the issue.
However, after configuring it, I am not sure how to tell whether PgBouncer is handling connections as intended (if at all) and in a healthy and effective way.
I think the key is deciding whether Djang's CONN_MAX_AGE
setting should be set to 0
or to None
. This answer says it should be None
for persistent connections. But wouldn't this defeat the purpose of PgBouncer's connection pooling? Wouldn't we want to set it to 0
and release connections back to the pool instead?
I set PgBouncer's verbosity level to 3, let me paste my log when CONN_MAX_AGE
is None
and when it is 0
, hopefully it tells you something relevant that I myself can't see:
CONN_MAX_AGE = None
2025-02-27 12:15:02.832 UTC [3336] NOISE safe_send(23, 577) = 577
2025-02-27 12:15:02.832 UTC [3336] NOISE sbuf_process_pending: end
2025-02-27 12:15:02.832 UTC [3336] NOISE resync(20): done=577, parse=577, recv=577
2025-02-27 12:15:02.832 UTC [3336] NOISE S-0x1a8a698: dbname/dbuser@127.0.0.1:5432 release_server: new state=12
2025-02-27 12:15:02.832 UTC [3336] DEBUG S-0x1a8a698: dbname/dbuser@127.0.0.1:5432 reuse_on_release: replication 0
2025-02-27 12:15:02.836 UTC [3336] NOISE resync(23): done=0, parse=0, recv=0
2025-02-27 12:15:02.836 UTC [3336] NOISE safe_recv(23, 4096) = 1044
2025-02-27 12:15:02.836 UTC [3336] NOISE sbuf_process_pending: start
2025-02-27 12:15:02.836 UTC [3336] NOISE sbuf_process_pending: loop 1
2025-02-27 12:15:02.836 UTC [3336] NOISE C-0x1a80a40: dbname/dbuser@127.0.0.1:59190 read pkt='Q' len=1044
2025-02-27 12:15:02.836 UTC [3336] NOISE C-0x1a80a40: dbname/dbuser@127.0.0.1:59190 find_server: client had no linked server yet
2025-02-27 12:15:02.836 UTC [3336] NOISE C-0x1a80a40: dbname/dbuser@127.0.0.1:59190 linking client to S-0x1a8a698
2025-02-27 12:15:02.836 UTC [3336] NOISE C-0x1a80a40: dbname/dbuser@127.0.0.1:59190 add_outstanding_request: added Q, still outstanding 1
2025-02-27 12:15:02.836 UTC [3336] NOISE sbuf_process_pending: loop 2
2025-02-27 12:15:02.836 UTC [3336] NOISE sbuf_process_pending: done looping
2025-02-27 12:15:02.836 UTC [3336] NOISE sbuf_send_pending_iobuf
2025-02-27 12:15:02.836 UTC [3336] NOISE tls_write: req=1044 out=1044
2025-02-27 12:15:02.836 UTC [3336] NOISE sbuf_process_pending: end
2025-02-27 12:15:02.836 UTC [3336] NOISE resync(23): done=1044, parse=1044, recv=1044
2025-02-27 12:15:02.837 UTC [3336] NOISE resync(20): done=0, parse=0, recv=0
2025-02-27 12:15:02.837 UTC [3336] NOISE tls_read: req=4096 out=577
2025-02-27 12:15:02.837 UTC [3336] NOISE sbuf_process_pending: start
2025-02-27 12:15:02.837 UTC [3336] NOISE sbuf_process_pending: loop 1
2025-02-27 12:15:02.837 UTC [3336] NOISE S-0x1a8a698: dbname/dbuser@127.0.0.1:5432 read pkt='T', len=419
2025-02-27 12:15:02.837 UTC [3336] NOISE sbuf_process_pending: loop 2
2025-02-27 12:15:02.837 UTC [3336] NOISE S-0x1a8a698: dbname/dbuser@127.0.0.1:5432 read pkt='D', len=138
2025-02-27 12:15:02.837 UTC [3336] NOISE sbuf_process_pending: loop 3
2025-02-27 12:15:02.837 UTC [3336] NOISE S-0x1a8a698: dbname/dbuser@127.0.0.1:5432 read pkt='C', len=14
2025-02-27 12:15:02.837 UTC [3336] NOISE sbuf_process_pending: loop 4
2025-02-27 12:15:02.837 UTC [3336] NOISE S-0x1a8a698: dbname/dbuser@127.0.0.1:5432 read pkt='Z', len=6
2025-02-27 12:15:02.837 UTC [3336] NOISE S-0x1a8a698: dbname/dbuser@127.0.0.1:5432 pop_outstanding_request: popped Q, still outstanding 0, skip 0
2025-02-27 12:15:02.837 UTC [3336] DEBUG C-0x1a80a40: dbname/dbuser@127.0.0.1:59190 query time: 1283 us
2025-02-27 12:15:02.837 UTC [3336] DEBUG C-0x1a80a40: dbname/dbuser@127.0.0.1:59190 transaction time: 1283 us
2025-02-27 12:15:02.837 UTC [3336] NOISE sbuf_process_pending: loop 5
2025-02-27 12:15:02.837 UTC [3336] NOISE sbuf_process_pending: done looping
2025-02-27 12:15:02.837 UTC [3336] NOISE sbuf_send_pending_iobuf
2025-02-27 12:15:02.837 UTC [3336] NOISE safe_send(23, 577) = 577
2025-02-27 12:15:02.837 UTC [3336] NOISE sbuf_process_pending: end
2025-02-27 12:15:02.837 UTC [3336] NOISE resync(20): done=577, parse=577, recv=577
CONN_MAX_AGE = 0
2025-02-27 12:24:45.527 UTC [3336] NOISE resync(20): done=20, parse=20, recv=20
2025-02-27 12:24:45.527 UTC [3336] NOISE resync(24): done=0, parse=0, recv=0
2025-02-27 12:24:45.527 UTC [3336] NOISE safe_recv(24, 4096) = 12
2025-02-27 12:24:45.527 UTC [3336] NOISE sbuf_process_pending: start
2025-02-27 12:24:45.527 UTC [3336] NOISE sbuf_process_pending: loop 1
2025-02-27 12:24:45.527 UTC [3336] NOISE C-0x1a80fd0: dbname/dbuser@127.0.0.1:34242 read pkt='Q' len=12
2025-02-27 12:24:45.527 UTC [3336] NOISE C-0x1a80fd0: dbname/dbuser@127.0.0.1:34242 add_outstanding_request: added Q, still outstanding 1
2025-02-27 12:24:45.527 UTC [3336] NOISE sbuf_process_pending: loop 2
2025-02-27 12:24:45.527 UTC [3336] NOISE sbuf_process_pending: done looping
2025-02-27 12:24:45.527 UTC [3336] NOISE sbuf_send_pending_iobuf
2025-02-27 12:24:45.527 UTC [3336] NOISE tls_write: req=12 out=12
2025-02-27 12:24:45.527 UTC [3336] NOISE sbuf_process_pending: end
2025-02-27 12:24:45.527 UTC [3336] NOISE resync(24): done=12, parse=12, recv=12
2025-02-27 12:24:45.527 UTC [3336] NOISE resync(20): done=0, parse=0, recv=0
2025-02-27 12:24:45.527 UTC [3336] NOISE tls_read: req=4096 out=18
2025-02-27 12:24:45.527 UTC [3336] NOISE sbuf_process_pending: start
2025-02-27 12:24:45.527 UTC [3336] NOISE sbuf_process_pending: loop 1
2025-02-27 12:24:45.527 UTC [3336] NOISE S-0x1a8a698: dbname/dbuser@127.0.0.1:5432 read pkt='C', len=12
2025-02-27 12:24:45.528 UTC [3336] NOISE sbuf_process_pending: loop 2
2025-02-27 12:24:45.528 UTC [3336] NOISE S-0x1a8a698: dbname/dbuser@127.0.0.1:5432 read pkt='Z', len=6
2025-02-27 12:24:45.528 UTC [3336] NOISE S-0x1a8a698: dbname/dbuser@127.0.0.1:5432 pop_outstanding_request: popped Q, still outstanding 0, skip 0
2025-02-27 12:24:45.528 UTC [3336] DEBUG C-0x1a80fd0: dbname/dbuser@127.0.0.1:34242 query time: 826 us
2025-02-27 12:24:45.528 UTC [3336] DEBUG C-0x1a80fd0: dbname/dbuser@127.0.0.1:34242 transaction time: 1948 us
2025-02-27 12:24:45.528 UTC [3336] NOISE sbuf_process_pending: loop 3
2025-02-27 12:24:45.528 UTC [3336] NOISE sbuf_process_pending: done looping
2025-02-27 12:24:45.528 UTC [3336] NOISE sbuf_send_pending_iobuf
2025-02-27 12:24:45.528 UTC [3336] NOISE safe_send(24, 18) = 18
2025-02-27 12:24:45.528 UTC [3336] NOISE sbuf_process_pending: end
2025-02-27 12:24:45.528 UTC [3336] NOISE resync(20): done=18, parse=18, recv=18
2025-02-27 12:24:45.528 UTC [3336] NOISE S-0x1a8a698: dbname/dbuser@127.0.0.1:5432 release_server: new state=12
2025-02-27 12:24:45.528 UTC [3336] DEBUG S-0x1a8a698: dbname/dbuser@127.0.0.1:5432 reuse_on_release: replication 0
2025-02-27 12:24:45.528 UTC [3336] NOISE resync(24): done=0, parse=0, recv=0
2025-02-27 12:24:45.528 UTC [3336] NOISE safe_recv(24, 4096) = 5
2025-02-27 12:24:45.528 UTC [3336] NOISE sbuf_process_pending: start
2025-02-27 12:24:45.528 UTC [3336] NOISE sbuf_process_pending: loop 1
2025-02-27 12:24:45.528 UTC [3336] NOISE C-0x1a80fd0: dbname/dbuser@127.0.0.1:34242 read pkt='X' len=5
2025-02-27 12:24:45.528 UTC [3336] LOG C-0x1a80fd0: dbname/dbuser@127.0.0.1:34242 closing because: client close request (age=0s)
2025-02-27 12:24:45.528 UTC [3336] NOISE safe_close(24) =
At first glance, I see no difference in the log, but there's much being logged too.
At least I have cleared my doubts on whether PgBouncer was being used at all, since I see some mentions to queries and transactions in the log when I test this on staging. All it took was changing the port to PgBouncer's.
If relevant at all, these are my PgBouncer settings:
[databases]
template1 = host=localhost port=5432 dbname=dbname
[pgbouncer]
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
logfile = /etc/pgbouncer/pgbouncer.log
pidfile = /etc/pgbouncer/pgbouncer.pid
pool_mode = transaction
max_client_conn = 250
min_pool_size = 10
reserve_pool_size = 10
reserve_pool_timeout = 2
The numbers feel a bit arbitrary, I set default_pool_size
to 250
because in the mentioned answer they say it should be below Postgres's max connections, which in my case is 300
.
Then they also set max_client_conn
to 1000
because they have a Gunicorn setup with eventlet, which is also my case. I am running it with 17 workers, but eventlet is single-threaded unless I am mistaken, so wouldn't max connections be 17 as well? How can they get so high? I am surely missing something.
To summarize:
Should I set CONN_MAX_AGE to
0
or toNone
?Do my PgBouncer logs show healthy behavior?
What values should I use for PgBouncer
default_pool_size
andmax_client_conn
, if I am running 17 eventlet Gunicorn workers?
Edit: I just realized each eventlet worker handles up to 1000 connections. Does this mean potentially 17000 concurrent database connections, if it weren't for Postgres's max_connections
?