Skip to main content
Metrics type: Key MetricsCategory: Capacity

At a glance

The share of your PostgreSQL connection budget that is already spoken for. Vortex IQ takes the count of active plus idle in transaction backends and divides it by max_connections. At 100% the next client that tries to connect is refused with FATAL: sorry, too many clients already, and from the application’s point of view the database is down even though postgres is healthy. This is the single most common way a busy PostgreSQL instance falls over, which is why it sits at the top of the Capacity panel.

Calculation

The gauge is computed live as:
where:
  • active_backends = count(*) FROM pg_stat_activity WHERE state = 'active'
  • idle_in_tx_backends = count(*) FROM pg_stat_activity WHERE state = 'idle in transaction' (plus 'idle in transaction (aborted)')
  • max_connections = the server’s hard ceiling from SHOW max_connections
A subtle but important detail: max_connections is not the number the application can use. PostgreSQL reserves superuser_reserved_connections (default 3) so a superuser can always log in to fix a mess, and replication and some background workers also draw from the budget. Vortex IQ divides by the raw max_connections so the gauge matches the figure PostgreSQL itself enforces, but the practical ceiling for ordinary clients is slightly lower. That is why we alert at 90% rather than 100%: the last few percent are effectively unusable. When a PgBouncer pool is present, the database-facing server_lifetime connections are usually a small, fixed set, so PostgreSQL’s own saturation stays low while the pooler’s client-side pool is the thing that fills up. In that topology this card stays calm and the pooler series is the one to watch. See PgBouncer Pool Saturation vs Traffic Burst.

Worked example

A platform team runs a primary PostgreSQL 15 instance on a managed service backing an order-management API. max_connections is set to 200. Snapshot taken on 14 Apr 26 at 19:45 BST, during an evening sales push. The numerator is 138 + 51 + 2 = 191. Divided by 200, that is 95.5% saturation. The gauge is red and the > 90% alert has fired. Two signals jump out. First, the raw pressure is genuinely high: 138 active backends means the instance is doing a lot of real work. But second, and more interesting, 53 backends are idle in transaction or aborted. Those are connections that opened a transaction, ran a statement, then went quiet without committing or rolling back, usually because the application is doing slow work (an external API call, a report render) inside a database transaction. They are holding a pool slot and any locks they took, contributing nothing.
The team’s response is layered:
  1. Immediate relief: kill the worst idle in transaction offenders. SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle in transaction' AND state_change < now() - interval '5 minutes'; reclaims roughly 40 slots in one shot, dropping saturation to about 75%. Pair this with Idle-in-Transaction Backends.
  2. Structural fix: the real cause is the application holding transactions open across external calls. The fix is in the application (commit before the slow call, not after), not in the database. Raising max_connections would only delay the next wall and costs memory: each backend reserves work_mem and more.
  3. Right architecture: put PgBouncer in transaction-pooling mode in front of the instance so 2,000 application clients can share 200 server connections. After that, this card stays green and the pooler card becomes the one to watch.
The lesson the team takes away: a high saturation reading is rarely “we need more connections”. It is almost always “something is holding connections it should have released”, and the cure is in the application or a pooler, not in max_connections.

Sibling cards

Reconciling against the source

Where to look in PostgreSQL itself:
Run SELECT count(*), state FROM pg_stat_activity GROUP BY state; to see the live breakdown by backend state. Add the active and idle in transaction rows and divide by the value of SHOW max_connections; to reproduce the gauge by hand. Run SELECT * FROM pg_stat_database WHERE datname = current_database(); for connection-level counters at the database level. On a managed service, the console exposes the same figure as a “Database Connections” metric: AWS RDS / Aurora publishes DatabaseConnections in CloudWatch, Google Cloud SQL publishes database/postgresql/num_backends, and Azure Database for PostgreSQL publishes active_connections.
Why our number may legitimately differ from a hand count:

Known limitations / FAQs

The gauge says 95% but the application is not erroring. Are we safe? You are close to the edge, not over it. Refusals only start at the practical ceiling (max_connections minus reserved and replication slots). At 95% you have a handful of slots left, which one bad deploy, a connection leak, or a traffic burst can consume in seconds. Treat 90%+ as “act now”, not “wait for errors”. Should I just raise max_connections to make this go away? Rarely the right move. Every connection reserves memory (work_mem per sort or hash, plus per-backend overhead), so raising the ceiling can push the instance into swapping, which is far worse than connection refusals. The correct fix for sustained saturation is a connection pooler (PgBouncer in transaction mode) and fixing whatever holds connections open. Watch Memory Usage % before and after any change. Why are idle in transaction backends counted but plain idle ones are not? A plain idle backend is reclaimable: the pooler or the next request can reuse it immediately, and it holds no locks. An idle in transaction backend is the dangerous kind: it is sitting on an open transaction, holding any locks it took and pinning a slot, often because the application started a transaction and then went off to do slow non-database work. That is the pressure this card is built to surface. We run PgBouncer. This card stays green even under heavy load. Is it broken? No, that is expected and correct. With PgBouncer in transaction-pooling mode, a small fixed set of server connections handles thousands of client connections, so PostgreSQL’s own saturation stays low. The saturation that matters in that topology is the pooler’s client-side pool, which Vortex IQ tracks separately. Use PgBouncer Pool Saturation vs Traffic Burst. Does this include connections from read replicas or the standby? The gauge is per-instance. The walsender backends that stream WAL to standbys do appear in the primary’s pg_stat_activity and count toward its ceiling, so a primary with several streaming standbys carries a small fixed overhead. Each replica’s own client connections are measured against that replica’s ceiling, not the primary’s. The managed-service console shows a different connection count than this card. Common and usually benign. Managed services run their own maintenance and monitoring backends that occupy slots, and some console metrics report against the practical ceiling (after reserved connections) while Vortex IQ divides by raw max_connections. Reconcile by running SELECT count(*), state FROM pg_stat_activity GROUP BY state; directly: that is the ground truth both numbers derive from. Can I change the 90% alert threshold? Yes. The threshold is configurable per profile in the Alert Rules tab. A read-heavy reporting database with a generous ceiling and a pooler in front might tolerate 95%; a tightly provisioned OLTP primary might want to alert at 80% to leave room for failover reconnection storms. Set it to your real baseline rather than the generic default.

Tracked live in Vortex IQ Nerve Centre

Connection Pool Saturation % is one of hundreds of KPI pulses Vortex IQ tracks across PostgreSQL and 70+ other ecommerce connectors. Nerve Centre runs the detection layer; Vortex Mind investigates the cause when something moves; Ask Viq lets you interrogate any number in plain English. Start for free or book a demo to see this metric running on your own data.