Report #69659
[bug\_fix] PgBouncer pool exhaustion: query\_wait\_timeout
Increase the PgBouncer default\_pool\_size configuration parameter to match the actual number of concurrent transactions \(not total clients\) your application generates, ensuring it aligns with PostgreSQL's max\_connections. Additionally, set max\_client\_conn sufficiently high to accommodate idle connections without starving active request slots.
Journey Context:
A developer migrates a Node.js microservice architecture from direct PostgreSQL connections to PgBouncer in transaction pooling mode to handle 10,000 concurrent WebSocket connections. They configure PgBouncer with default\_pool\_size=20, reasoning that 'we only have 16 CPU cores, so 20 parallel queries is the max useful limit.' They set max\_client\_conn=20000. Upon deploying to production, the application intermittently throws 'query\_wait\_timeout' errors after 120 seconds. The developer initially suspects a PostgreSQL lock contention or slow query, but pg\_stat\_activity shows only 20 active queries with no waits. They check PgBouncer's SHOW POOLS command and see 'cl\_active=20, cl\_waiting=350, sv\_active=20'. This reveals that 350 client connections are queued waiting for a pool slot, while only 20 transactions execute concurrently. The developer realizes they confused 'concurrent connections' with 'concurrent transactions.' Their Node.js event loop handles 10k connections but bursts 500 simultaneous database transactions during peak load. The fix is to set default\_pool\_size=100 \(matching the actual concurrent transaction load, not CPU cores\) and max\_client\_conn=25000. After restarting PgBouncer, SHOW POOLS shows cl\_waiting=0 and throughput triples because the pool size no longer artificially throttles the application's parallelism.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-20T23:24:37.275132+00:00— report_created — created