The High Cost of Saying "Hello"
Opening a database connection is not free. In PostgreSQL specifically, each new connection causes the server to fork a new operating system process, allocate a chunk of memory (typically 5–10 MB per connection), and complete an authentication handshake. This takes measurable CPU time and memory.
A web application under load might have 500 concurrent PHP or Python worker processes, each wanting its own database connection. That's 500 forked processes on the database server, consuming several gigabytes of RAM just for connection overhead — before a single query runs. At 2,000 concurrent connections, PostgreSQL typically becomes unstable regardless of how powerful the hardware is.
The Solution: Connection Pooling
A connection pooler is a proxy that sits between your application and the database. It maintains a small, fixed pool of long-lived connections to the database and multiplexes many application clients onto them.
The flow works like this:
1. PgBouncer opens and maintains, say, 50 persistent connections to PostgreSQL.
2. When an application worker needs to run a query, it connects to PgBouncer (fast — no fork, no auth overhead on the DB side) and borrows one of the 50 connections.
3. When the query (or transaction) completes, the connection is returned to the pool and immediately available for another worker.
The database sees a stable 50 connections. The application sees 500 "connections" that are actually multiplexed. PgBouncer is the industry-standard connection pooler for PostgreSQL, written in C and capable of handling tens of thousands of client connections with minimal overhead. A minimal configuration points it at the real PostgreSQL host, sets pool_mode = transaction, and caps default_pool_size — the application then connects to PgBouncer's listen port instead of PostgreSQL directly, and PostgreSQL itself never sees more than the configured pool size.