Skip to content
Navigation

Type at least two characters. Search covers page titles, headings, tags and database names.

↑ ↓ to navigateEnter to openEsc to close0 pages
Production Best Practicesintermediate

Connection Pooling

Why connection count is a capacity limit, how to size a pool, and the pooling modes that break session-dependent features.

3 min readIntermediateUpdated Edit this page

A database connection is not free. In PostgreSQL it is an operating system process with its own memory; in MySQL it is a thread with per-thread buffers. Thousands of mostly-idle connections cost memory and scheduler time whether or not they are doing work.

Sizing the pool

The instinct is to raise the pool size when the database is slow. That usually makes it slower: more concurrent queries contend for the same CPUs and disks, each runs longer, and the queue grows.

A workable starting point for a CPU-bound OLTP workload is a small multiple of the core count — often in the range of two to four connections per core — then adjust based on measurement. Workloads that spend much of their time waiting on I/O tolerate more; workloads that are pure CPU tolerate fewer.

What matters more than the formula:

  • Total across all instances. Twenty application pods with a pool of 20 each is 400 connections, not 20. Size the per-instance pool from the total the database can serve, divided by the maximum instance count including deploys that briefly double it.
  • Separate pools per workload. Give background jobs and reporting their own, smaller pool, so a slow batch cannot consume every connection the user-facing path needs.
  • A reserved administrative margin. PostgreSQL's superuser_reserved_connections exists so you can still connect when the application has exhausted everything else.

Pooling modes

ModeConnection held forConcurrency gainBreaks
SessionThe client's whole sessionLowNothing
TransactionOne transactionHighSession state, prepared statements, advisory locks, LISTEN/NOTIFY
StatementOne statementHighestMulti-statement transactions

Transaction pooling is what makes a pooler dramatically effective — hundreds of client connections share a few dozen server connections, because most clients are idle between transactions. It is also where applications break, because anything that assumes session continuity is no longer guaranteed to see the same backend.

Where to put the pooler

  • In the application (HikariCP, pgx, database/sql) — simplest, but the total connection count grows with instance count.
  • As a sidecar — one pooler per application pod. Keeps the network hop local and still scales with instances.
  • Centralised (PgBouncer, ProxySQL) — the only arrangement that genuinely caps total connections. It is then a component on the critical path, so it needs its own redundancy and monitoring.

Application-level and centralised pooling combine well: a small local pool per instance in front of a central pooler that enforces the global ceiling. See PgBouncer.