Skip to content
dbexplore

Free tool

How many connections does this server actually need?

Your transaction rate and your transaction time already say how many connections do any work. Everything above that is waiting, and the gap between what your pools open and what the server accepts is where the refusals come from.

Run it on your numbers

Without hyperthreads. The heuristic this feeds counts real cores.

xact_commit plus xact_rollback over the interval, at the peak rather than the average.

From BEGIN to COMMIT, including the application thinking between statements — not the mean statement time, which is the one your dashboard shows.

Added in PostgreSQL 16, for roles granted pg_use_reserved_connections. It comes off the same total as the superuser reservation, not instead of it.

Zero when the working set is fully cached. On solid-state storage this is a guess, and the wiki page the heuristic comes from says so itself.

Every replica, worker, cron container and admin console. The count that matters is the one during a rolling deploy, when old and new are both up.

Measure it rather than take a number from anywhere, including here: it depends on what that connection has already run. pg_backend_memory_contexts is the honest way to find yours.

Three ceilings, and only one of them is in your config

The number of connections doing work at any instant is your transaction rate multiplied by how long a transaction stays open. That is Little's law and it is not an approximation; it holds for any queueing system in steady state. Five hundred transactions a second, each open for twenty milliseconds, is ten connections busy. Not two hundred. Ten.

The second ceiling is what the machine can genuinely run at once, which the PostgreSQL wiki puts at roughly twice the core count plus an allowance for storage concurrency. That page is refreshingly honest that nobody has checked how the formula behaves on solid-state storage, so treat it as an order of magnitude. The third is what the server will let an ordinary role open, and it is the only one you configured. When the first is small and the third is large, the middle one is what you are really running into.

Where the refusals come from

Almost nobody hits a connection limit from one process. They hit it from six, or sixteen, each holding a pool sized without reference to the others, on an afternoon when a rolling deploy has the old set and the new set up at the same time. The arithmetic is trivial once it is written down and it is essentially never written down, which is why the error arrives as a mystery in one instance rather than as a capacity plan.

What the server accepts is also not max_connections. A few slots are always held back for superusers so that an administrator can still get in when the application has taken everything. From PostgreSQL 16 there is a second reservation as well, for roles granted the privilege to use it, and it comes out of the same total rather than instead of the first. It defaults to zero, so it costs nothing until somebody sets it, and then it lowers a ceiling that appears nowhere in any application's configuration.

What transaction mode takes away

A pooler in session mode gives you almost none of the multiplication people expect from pooling: the client holds its server connection until it disconnects. Transaction mode is where the gain is, and it is the mode that quietly breaks things. Server-side prepared statements go first, on the afternoon of the change. Session-level settings go next, because the next statement may land on a different server connection than the one you set them on.

Then come the ones found weeks later, in something nobody was watching: advisory locks taken outside a transaction and held by a connection that is no longer yours, notifications that needed the same connection to still be listening, temporary tables that vanish between statements, and cursors declared to outlive their transaction. None of these is a reason to avoid transaction mode. All of them are a reason to find out which of them your application does before you switch, rather than after.

Nobody wants to be the one who ran the query too late.

DBExplore watches the numbers behind these checks on every cluster it is pointed at, and asks before it changes anything. We onboard design partners in small batches.