Dev & EngARTICLE

PostgreSQL 16's reserved_connections guarantees a connection for monitoring and backup when the application exhausts the pool

Released in PostgreSQL 16, in 2023, the parameter creates a second connection reserve, separate from the superuser's, so that monitoring, backup, and DBA can still get in when the application consumes the whole pool.

PostgreSQL 16's reserved_connections guarantees a connection for monitoring and backup when the application exhausts the pool
Image: Roberto Diniz

Christophe Pettus, a PostgreSQL consultant at PGX Inc., published on Planet PostgreSQL a technical breakdown of reserved_connections, a parameter introduced in PostgreSQL 16 (released in 2023) and still little used in production. The piece is part of a series on GUCs (Postgres's configuration parameters) and stems from a concrete problem: the application saturates max_connections, and at that moment monitoring, the backup's pg_dump, and the human holding the pager find out they can't get in either.

The reserve that protected the wrong account

Since PostgreSQL 7.3, superuser_reserved_connections has reserved a few connections, but only for accounts with the SUPERUSER attribute. Pettus notes that this protects the wrong account: nobody connects to production as superuser if they can avoid it, and on a managed service nobody can anyway. Who needs to get in when the pool is full is the monitoring agent, the backup job, and the on-call DBA, all with common roles, without superuser privilege.

These roles end up locked out right alongside the application. reserved_connections solves this by creating a second reserve, positioned ahead of the superuser reserve, for roles chosen by the administrator.

A gate, not a quota

The mechanism is not a quota reserved in advance: it's a gate checked at connection time. The new backend has already taken a slot from the regular pool after authenticating; only then does Postgres count how many free slots are left and compare that number to the two reserves.

If fewer free slots remain than superuser_reserved_connections, only a superuser can keep the connection; any other role gets FATAL: remaining connection slots are reserved for roles with the SUPERUSER attribute. If the number remaining falls between the two reserves combined, the connection goes to superusers and to roles with the privileges of the predefined role pg_use_reserved_connections; any other role gets FATAL: remaining connection slots are reserved for roles with privileges of the "pg_use_reserved_connections" role.

Superusers skip this entire check, which means they can also consume all the slots and then, yes, run into the generic sorry, too many clients already like anyone else. All three messages carry the same SQLSTATE 53300, so any connection-exhaustion alert that already monitors that code also catches this case.

The arithmetic in numbers

With max_connections = 100, superuser_reserved_connections at its default of 3, and reserved_connections = 10, the distribution looks like this:

RoleEffectively available slots
Common roles (application)87
Members of pg_use_reserved_connections97
Superusers100

The number the gate sees is the free slots in the regular pool: max_connections minus every client backend that already occupies one, including connections still authenticating (which pg_stat_activity doesn't show before version 18) and connections already exiting. Existing connections from privileged roles are not deducted from the reserve, so a monitoring role with a connection leak can eat its own reserve without noticing. The parameter guarantees exactly one thing: unprivileged roles don't take the last N slots. Nothing beyond that.

Configuring it in practice

The parameter's context is postmaster, so enabling it requires restarting the server, and the moment you discover you need the reserve is never a moment when restarting is an option. Planning has to come before the incident, not after.

sql
-- postmaster context: requires server restart
ALTER SYSTEM SET reserved_connections = 10;
sql
GRANT pg_use_reserved_connections TO monitoring_role, dba_role, backup_role;

The value constraint is strict: superuser_reserved_connections plus reserved_connections must be strictly less than max_connections; if it isn't, postmaster refuses to start up and explains why. In PostgreSQL 14 and 15, which don't have the parameter, the superuser reserve remains the only one available.

One detail that costs dearly: the check uses the same inheritance-based membership verification as the rest of the privilege system. A NOINHERIT role that has been granted pg_use_reserved_connections is refused exactly like a role that was never granted it, and SET ROLE doesn't fix it, because the refusal happens before a session exists to run SET ROLE. It's worth checking rolinherit on the protected role, and, since version 16, the INHERIT option on the grant itself, before trusting that the GRANT works.

What the reserve doesn't cover

Walsenders, autovacuum workers, and background workers (parallel workers included) each use their own pool of process slots and don't enter this count or this check. A streaming replica or a pg_basebackup connects even when a common role can't, because they don't compete for the same pool; pg_dump running as a common role, however, is just another client and gets left out if the reserve is closed.

Up through the February 2025 maintenance releases (16.7, 17.3, 15.11, and 14.16), this exclusion had a flaw: a parallel worker spawned on behalf of an unprivileged user, launched while the pool was already inside a reserved range, could be refused and take the query down with it. Anyone behind on maintenance patches carries this risk without knowing it.

Where the parameter came from

reserved_connections was born from a January 2023 email from Robert Haas listing what still kept an administrator with CREATEROLE from serving as a usable substitute for the superuser. One of the points: CONNECTION LIMIT limits each role individually, and no sum of individual limits jointly reserves slots for the role that needs to get in last. Nathan Bossart's patch was applied four days later, which gives a sense of how old the gap already was.

Managed services got there first: RDS already solved the same problem for its quasi-superuser with rds.rds_superuser_reserved_connections (default 2, for rds_superuser), which is why the non-superuser connection count on RDS is max_connections minus 3 minus 2, not just minus 3.

What to do about it

It's also worth fixing an account used when sizing max_connections: since PostgreSQL 12, walsenders come from their own pool, sized by max_wal_senders, and don't count against max_connections. Anyone who has already added replication connections into that count should take them out of the calculation.

Pettus's practical recommendation is direct: turn on reserved_connections, sizing it to the same handful of slots you'd already informally reserve for humans and monitoring; grant pg_use_reserved_connections to the monitoring role, the DBA role, and the role that runs pg_dump; and never grant that privilege to the application role, because an application that reaches the reserve stops having a reserve.

Keep superuser_reserved_connections at 3 as a last line behind this, and keep per-role CONNECTION LIMIT on the application side: the two mechanisms work from opposite ends toward the same guarantee. With the parameter at zero, as it ships by default, nobody is protected except the superuser account that nobody uses in production.

Translated from the Brazilian Portuguese original · Read the original