At a glance
The share of your PostgreSQL connection budget that is already spoken for. Vortex IQ takes the count ofactiveplusidle in transactionbackends and divides it bymax_connections. At 100% the next client that tries to connect is refused withFATAL: sorry, too many clients already, and from the application’s point of view the database is down even thoughpostgresis 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.
| What it tracks | Pool occupancy expressed as a percentage of the hard connection ceiling. The numerator is live backends doing work or holding an open transaction; the denominator is the configured ceiling. |
| Data source | (active + idle-in-tx) / max_connections, where the numerator is counted from pg_stat_activity (state IN ('active','idle in transaction')) and the denominator is current_setting('max_connections'). If a PgBouncer or pgpool front end is detected, its pools are tracked separately rather than folded into this number. |
| Time window | RT/1m (real-time gauge, smoothed over a rolling 1-minute window so a single spiky sample does not trip the alert). |
| Alert trigger | > 90%. Sustained occupancy above 90% means you are one traffic burst away from connection refusals. |
| Roles | owner, engineering, operations |
| What does NOT count | Plain idle backends (connected but between transactions) are excluded from the numerator because they are reclaimable. They still consume a slot, so they matter for the raw count, but they are not the saturation pressure this card warns about. |
| PgBouncer / pgpool | If a pooler sits in front of PostgreSQL, the pooler’s own client-side pool can saturate long before max_connections does. Vortex IQ surfaces the pooler pool as its own series so you see both layers. |
Calculation
The gauge is computed live as: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 fromSHOW max_connections
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.
| Backend state | Count |
|---|---|
active | 138 |
idle in transaction | 51 |
idle | 9 |
idle in transaction (aborted) | 2 |
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.
- Immediate relief: kill the worst
idle in transactionoffenders.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. - 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_connectionswould only delay the next wall and costs memory: each backend reserveswork_memand more. - 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.
max_connections.
Sibling cards
| Card | Why pair it with Connection Pool Saturation | What the combination tells you |
|---|---|---|
| Connections In Use | The raw backend count behind the percentage. | The absolute number lets you see how much headroom (in connections, not percent) you actually have. |
| Idle-in-Transaction Backends | Isolates the reclaimable portion of the numerator. | High saturation that is mostly idle-in-tx is an application transaction-scoping bug, not a capacity problem. |
| Connection Pool at >90% Saturation | The alert-feed view of this same threshold. | Confirms whether the breach is a momentary spike or a sustained event worth paging on. |
| Connection Errors (24h) | Counts the “too many clients” refusals that follow a saturation event. | A spike here after a saturation breach is the proof that clients were actually turned away. |
| PgBouncer Pool Saturation vs Traffic Burst | The pooler-layer equivalent when a front end is present. | Tells you which layer is the bottleneck: the pooler’s client pool or PostgreSQL itself. |
| Memory Usage % | Each connection reserves working memory. | Raising max_connections to fix saturation pushes this card up; the two trade off directly. |
| Queries per Second (live) | The traffic driving the pool. | A QPS spike with rising saturation is healthy load; flat QPS with rising saturation is a connection leak. |
| PostgreSQL Health Score | The composite that weights pool headroom. | Sustained saturation above 90% pulls the composite down on its own. |
Reconciling against the source
Where to look in PostgreSQL itself:RunWhy our number may legitimately differ from a hand count:SELECT count(*), state FROM pg_stat_activity GROUP BY state;to see the live breakdown by backend state. Add theactiveandidle in transactionrows and divide by the value ofSHOW max_connections;to reproduce the gauge by hand. RunSELECT * 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 publishesDatabaseConnectionsin CloudWatch, Google Cloud SQL publishesdatabase/postgresql/num_backends, and Azure Database for PostgreSQL publishesactive_connections.
| Reason | Direction | Why |
|---|---|---|
| Sampling moment | Either way | pg_stat_activity is a point-in-time snapshot. Vortex IQ smooths over a 1-minute window; your manual SELECT catches one instant, which may be higher or lower. |
| Idle exclusion | Vortex IQ lower | The gauge excludes plain idle backends from the numerator; a naive count(*) FROM pg_stat_activity counts everything and reads higher. |
| Managed-service overhead | Vortex IQ may read higher | RDS, Cloud SQL and Azure run their own monitoring and maintenance backends that occupy slots. These appear in pg_stat_activity and count toward the ceiling. |
| Reserved connections | Console may differ | Some console metrics report against the practical ceiling (minus reserved); Vortex IQ divides by raw max_connections to match what PostgreSQL enforces. |
| Replication slots | Vortex IQ may read higher | walsender processes for streaming standbys consume connection slots and appear in the 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 Sensitivity 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.