Hitting Postgres connection limits is a common production problem. The root cause isn't just memory — shared data structures like the process array create global bottlenecks that degrade performance as connection counts grow, which is why cloud providers cap connections even on large instances. Three main strategies help: (1) node-local connection pools (built into Go's database/sql, Java's JDBC, Active Record) to reuse connections and cap per-node usage; (2) minimum viable checkout — holding a connection only during core logic, not during request parsing, response serialization, or external API calls; and (3) PgBouncer as a global proxy pool, especially useful for apps that can't implement their own pooling (e.g., Rails on Unicorn). Transaction pooling mode in PgBouncer is the best fit for most apps, though it restricts use of SET, LISTEN/NOTIFY, and some prepared statement patterns. Developers should track per-node connections, cluster-wide totals, and expected connection counts during deploys. These principles apply beyond Postgres to any database.

11m read timeFrom brandur.org
Post cover image
Table of contents
The practical limits of concurrencyTechniques for efficient connection useConnections as a resource
4 Impressions