dkduckkit.dev

PostgreSQL Connection Pool Sizer

Calculate optimal max_connections, work_mem, and PgBouncer configuration for your server. Get a ready-to-paste postgresql.conf snippet.

Total across all application instances

max_connections
20
work_mem
204MB
shared_buffers
4GB
RAM usage
30%
# postgresql.conf — generated by duckkit.dev/tools/db-connection-pool
# Server RAM: 16GB | Workload: OLTP

# Connection settings
max_connections = 20

# Memory settings
shared_buffers = 4GB
work_mem = 204MB
maintenance_work_mem = 819MB

# Query planner hint (not actual allocation)
effective_cache_size = 12GB

# Write-ahead log
wal_buffers = 16MB
checkpoint_completion_target = 0.9

# Parallel query (adjust based on CPU cores)
max_worker_processes = 8
max_parallel_workers_per_gather = 4
max_parallel_workers = 8

Why max_connections is not free

Every PostgreSQL connection is a full OS process. Even when completely idle, a connection consumes resource metadata and shared memory slots, equating to approximately 2-5MB of RAM per connection. When active, each running query requests exactly what it needs from work_mem, which means 500 connections executing complex sorts can easily wipe out your memory limits. This is exactly why keeping max_connections artificially high is dangerous.

Transaction vs session pooling

Your choice of connection pooler greatly dictates its efficiency. In session mode, a pooler ties its backend server connection to a client for the entire duration of the client connect. This is functionally similar to a direct database connection minus process startup time.

In transaction mode, however, a server connection is assigned to a client only during an active transaction and released immediately after. This multiplexing method can allow 100 actual PostgreSQL connections to handle 5,000+ client application connections seamlessly.

The work_mem trap

A common mistake is configuring work_mem to be too large. Remember that work_memis allocated per sort/hash operation, not per query. A parallelized query with multiple joins might invoke 5-10 operations simultaneously, multiplying the memory allocated. This multiplicative effect is the primary cause of out-of-memory (OOM) crashes under sudden load spikes.

When to add PgBouncer

As a strict rule of thumb, you should strongly consider PgBouncer or Odyssey once your application scale requires more than 50-100 max_connections on your database server. By reducing the number of idle server processes, PostgreSQL requires less context switching and runs considerably faster.

See how PostgreSQL latency fits your overall system budgetLearn more about PgBouncer

Frequently asked questions

What is the right max_connections for PostgreSQL?
A common rule of thumb: max_connections = RAM_GB × 100 / work_mem_MB. For a 16GB server with 32MB work_mem, that is roughly 50 connections. More connections does not mean better performance — each idle connection reserves memory. Use PgBouncer in transaction mode to handle hundreds of application connections with far fewer PostgreSQL connections.
What is the difference between PgBouncer transaction and session mode?
In transaction mode, a server connection is held only for the duration of a single transaction, then returned to the pool. This allows 100+ application clients to share 20 server connections. In session mode, a server connection is held for the entire client session — providing little benefit over direct connections. Transaction mode is recommended for most web applications, except those using session-level features like advisory locks or prepared statements.
How much RAM does each PostgreSQL connection use?
Each PostgreSQL backend process uses approximately 5-10MB of RAM in idle state. Under load, it additionally allocates work_mem for each sort or hash join operation. With parallel queries enabled, a single query can allocate work_mem × max_parallel_workers_per_gather. A 32MB work_mem with 4 parallel workers = 128MB per query in the worst case.
What should I set shared_buffers to?
The standard recommendation is 25% of total RAM, up to a maximum of 8GB. Beyond 8GB, PostgreSQL does not benefit significantly because it relies on the OS page cache (represented by effective_cache_size) for the remainder. Setting shared_buffers too high can reduce OS cache available to PostgreSQL, paradoxically hurting performance.
When should I use no connection pooler?
Direct connections without a pooler are acceptable for low-connection workloads (fewer than 50 simultaneous connections) and for OLAP/batch jobs where connection overhead is negligible compared to query duration. For web APIs with more than 50 concurrent users, PgBouncer in transaction mode is strongly recommended.