PgBouncer’s reserve pool is overflow capacity: extra server connections beyond pool_size that PgBouncer may open when clients have waited too long. Used as designed, it absorbs a short traffic spike and goes quiet. Used as a crutch, it hides a chronically undersized pool for months, until the day both base pool and reserve are exhausted and the queuing cliff is steeper than it would have been otherwise.
This article covers the activation mechanism, how to detect reserve usage, how to tell healthy burst absorption from chronic undersizing, and what to change when it is the latter.
For the broader saturation picture, see PgBouncer pool utilization high: sv_active approaching pool_size before clients queue. For the pooling model, see How PgBouncer actually works in production: a mental model for operators.
What the reserve pool is
Two settings control it:
reserve_pool_size: how many additional server connections a pool may open beyond itspool_size. Default is0, so the reserve pool is disabled entirely. If you have never set this, nothing in this article is happening on your system.reserve_pool_timeout: how long a client must wait in the queue before PgBouncer may draw from the reserve. Default is5seconds. This is a global setting, not per-database.
The total server connection ceiling for a pool is pool_size + reserve_pool_size, capped further by max_db_connections (per database) and max_user_connections (per user). If max_db_connections is lower than pool_size + reserve_pool_size, the reserve connections you think you have may never be created.
A version note that matters for monitoring: in PgBouncer 1.24.0, the per-database parameter was renamed from reserve_pool to reserve_pool_size, and the SHOW DATABASES output column changed with it. The old name is still accepted as a config alias. If you parse SHOW DATABASES by column name (or use an exporter that does), check which column your version emits; tooling written against the old name silently stopped reporting this value on 1.24.0 and later. Also in 1.24.0, reserve_pool_size became settable per user, in addition to globally and per database.
How activation works
The trigger is not pool fullness. The trigger is a waiting client. The sequence:
- A client sends a query and no server connection is free, so the client enters the pool’s FIFO wait queue.
- The client waits. While the wait is shorter than
reserve_pool_timeout(default 5 seconds), PgBouncer does nothing special. - Once the client has waited longer than
reserve_pool_timeout, PgBouncer may open an additional server connection from the reserve, up toreserve_pool_sizeextra connections, subject to themax_db_connectionsandmax_user_connectionscaps. - When load subsides, those extra connections return to normal lifecycle rules and the pool shrinks back toward
pool_size.
flowchart TD
A[Client sends query] --> B{Free server connection in pool?}
B -- yes --> C[Assign and execute]
B -- no --> D[Client enters FIFO wait queue]
D --> E{Waited longer than reserve_pool_timeout?}
E -- no --> D
E -- yes --> F{reserve_pool_size > 0 and reserve slots free?}
F -- yes --> G[Take connection from reserve_pool]
G --> C
F -- no --> H[Keep waiting]
H --> I{Wait exceeds query_wait_timeout?}
I -- no --> D
I -- yes --> J[Client disconnected with error]Two consequences follow:
- Activation implies waiting already happened. Reserve connections only appear after at least one client has been blocked for more than
reserve_pool_timeout. If you see reserve usage, you also hadcl_waiting > 0andmaxwaitabove the timeout. The reserve pool does not prevent queuing; it responds to it. - The reserve is a delay, not a cure. When
pool_size + reserve_pool_sizeis fully in use, clients queue again and eventually hitquery_wait_timeout(default 120 seconds) and are disconnected. The reserve buys headroom for a spike; it does not change the shape of the failure when demand keeps climbing.
How to detect reserve pool activation
There is no dedicated “reserve pool in use” counter. Detection is a comparison plus a log signal.
# 1. Per-pool server connection totals
psql -h /var/run/postgresql -p 6432 -U pgbouncer pgbouncer -Atc "SHOW POOLS;"
# 2. Configured pool_size per database
psql -h /var/run/postgresql -p 6432 -U pgbouncer pgbouncer -Atc "SHOW DATABASES;"
# 3. Confirm the reserve settings actually in effect
psql -h /var/run/postgresql -p 6432 -U pgbouncer pgbouncer -Atc "SHOW CONFIG;" | grep -E "reserve_pool|pool_size"
# 4. Log evidence of reserve draws
grep -ic "taking connection from reserve_pool" /var/log/pgbouncer/pgbouncer.log
For each pool, sum sv_active + sv_idle + sv_used + sv_tested + sv_login from SHOW POOLS. If that total exceeds the pool_size shown in SHOW DATABASES, the excess came from the reserve. Equivalently, an sv_active / pool_size ratio above 100% is only possible with reserve connections in play.
PgBouncer also logs a line of the form taking connection from reserve_pool each time it draws one. This is the better signal for frequency of activation, but note the failure mode: in a real pileup, every queued client that crosses the 5 second mark triggers a draw, and operators have reported over a hundred thousand of these lines in a couple of minutes during an application-side incident. If your logs suddenly fill with this message, the reserve is being hammered, and the log volume itself can become a secondary problem.
One caveat on PgBouncer 1.22.0 or later: default_pool_size can be set to 0 for an unlimited base pool.
Brief activation vs sustained activation
This is the decision the whole article exists for.
| Pattern | What it means | What to do |
|---|---|---|
| Reserve connections appear for seconds to under a minute during a traffic spike, then disappear | Intended use. The reserve did its job. | Nothing, beyond noting the spike. |
| Reserve in use for more than about 5 minutes continuously | Base pool_size is too small for current sustained load. | Plan a pool_size increase. |
| Reserve drawn at roughly the same times every day (peak hours, batch windows) | Chronic undersizing. The reserve is part of your steady-state capacity now. | Treat reserve usage as your real pool_size and resize accordingly. |
Reserve fully consumed AND cl_waiting still growing | Both base and reserve are exhausted. | Active incident. See the pool exhaustion guide. |
Brief activation is informational. Reserve connections in use for more than 5 minutes sustained is a ticket-level signal that the pool is undersized.
Why sustained reserve usage is dangerous
The reserve pool masks the real number. If your dashboards and capacity reviews look at sv_active / pool_size and mentally cap at 100%, a pool that routinely runs at 130% of pool_size (because the reserve quietly covers the gap) never shows up as a sizing problem. Growth continues, the reserve absorbs it, and the first visible symptom arrives only when demand exceeds pool_size + reserve_pool_size, at which point:
- Clients queue with no further overflow to draw on.
maxwaitclimbs towardquery_wait_timeout(default 120 seconds), after which clients are disconnected. See PgBouncer query_wait_timeout: clients disconnected after waiting too long for a connection.- Applications with shorter timeouts than
query_wait_timeoutgive up and retry, adding new waiters and new client connections: the retry cascade described in PgBouncer pool exhaustion: clients queue, wait times climb, and the retry cascade.
The queuing behavior at that point is cliff-edge, and the cliff is taller than it would have been for a correctly sized base pool, because you deferred the resize decision for as long as the reserve held.
Pooling mode changes the risk calculus. In session mode, server connections are held for the entire client session, so reserve connections are returned slowly and reserve exhaustion is much more likely under the same client count. In transaction mode, connections turn over per transaction, so a small reserve stretches much further. If you run session mode with a reserve pool, treat any reserve activation as more serious than the same event in transaction mode.
Signals to watch in production
| Signal | Why it matters | Warning sign |
|---|---|---|
Total server connections per pool vs pool_size | Direct detection of reserve usage: total above pool_size means reserve is active | Total exceeds pool_size for more than a minute |
sv_active / pool_size ratio | A ratio above 100% is only possible with reserve connections | Ratio above 100% sustained; ratio persistently 85-100% predicts reserve use |
cl_waiting per pool | Reserve activation requires a waiting client, so queue depth precedes and accompanies it | Any sustained non-zero value |
maxwait (SHOW POOLS) | The oldest waiter’s age; must exceed reserve_pool_timeout (default 5s) before the reserve kicks in | Values repeatedly crossing 5s; approaching application timeouts |
avg_wait_time (SHOW STATS / SHOW STATS_AVERAGES) | The queuing latency PgBouncer injects, which is what the reserve exists to cap | Sustained values well above baseline even while the reserve absorbs load |
taking connection from reserve_pool log lines | Frequency of reserve draws; the only per-event record | Any steady rate outside known spikes; sudden flood during incidents |
paused / disabled (SHOW DATABASES) | Context: during an administrative pause, queuing and reserve behavior look alarming but are expected | Check before escalating anything |
What to do when reserve usage is sustained
The fix is to size the base pool for the load you actually have, not to grow the reserve.
- Measure real demand. Look at peak
sv_activeincluding reserve connections over a representative week. That peak, plus burst headroom, is your targetpool_size. Utilization below 70% is healthy, 70-85% watch closely, above 85% act. - Check the PostgreSQL budget first. Every additional server connection consumes a
max_connectionsslot on the backend. Sumpool_size(plus reserve) across all pools and all PgBouncer instances targeting the same PostgreSQL, and keep the total comfortably belowmax_connectionsminus reserved superuser slots. Raisingpool_sizepast what PostgreSQL can serve trades queuing at the pooler for login failures at the backend. - Check
max_db_connectionsandmax_user_connections. If these caps are below your intendedpool_size + reserve_pool_size, raisingpool_sizealone does nothing. - Increase
pool_sizeandRELOAD. Pool size changes apply on reload; no restart needed. - Verify latency attribution before and after. Compare
avg_wait_timeagainstavg_query_time. Ifavg_query_timeis high, the backend is slow and a bigger pool mostly buys you more concurrent slow queries; fix the queries first. Ifavg_query_timeis fine andavg_wait_timecarries the latency, the resize is the right call. See PgBouncer avg_wait_time high: the latency the pool itself is injecting. - Keep the reserve small and in place. After resizing, the reserve returns to its intended role: absorbing the next genuine spike, not carrying daily peak.
Do not respond to sustained reserve usage by increasing reserve_pool_size. That deepens the masking effect and makes the eventual cliff worse.
How Netdata helps
- Netdata’s PgBouncer collector queries the admin console continuously, so per-pool
sv_active,sv_idle, and the other server connection states are captured as time series. “Total connections abovepool_size” becomes a trend, not a snapshot you had to be watching. - Per-pool breakdown matters here: one pool living in its reserve while nine others idle looks fine in any aggregate. Netdata charts split by pool so the chronic offender stands out.
- Correlating
cl_waiting,maxwait, andavg_wait_timeon the same dashboard shows the full activation chain: clients waited, maxwait crossedreserve_pool_timeout, the reserve opened, and wait time either recovered (healthy spike) or stayed elevated (undersized pool). - Baseline views of
avg_query_timenext toavg_wait_timelet you confirm whether reserve events are a sizing problem (wait high, query time normal) or a backend problem (both high) before you touchpool_size. - Alerting on duration, not just presence, of reserve usage maps directly to the brief-versus-sustained decision: a few minutes of reserve draw during a deploy should not page anyone; 30 minutes every evening should open a ticket.
Related guides
- PgBouncer avg_wait_time high: the latency the pool itself is injecting
- How PgBouncer actually works in production: a mental model for operators
- PgBouncer maxwait high: the oldest client waiter and how close it is to timing out
- PgBouncer monitoring checklist: the signals every connection pooler needs
- PgBouncer monitoring maturity model: from survival to expert
- PgBouncer pool exhaustion: clients queue, wait times climb, and the retry cascade
- PgBouncer pool utilization high: sv_active approaching pool_size before clients queue
- PgBouncer query_wait_timeout: clients disconnected after waiting too long for a connection






