Skip to main content
Metrics type: Cross-Platform MetricsCategory: Cross-Platform: Revenue at Risk

At a glance

PgBouncer Pool Saturation vs Traffic Burst overlays connection-pool saturation against the storefront traffic curve, row by row over a rolling 15-minute window. It answers the question that matters during a sale: “when the front-end sends a wave of shoppers, does the database connection pool have room to serve them, or is it about to start queuing?” A pool sitting at 95% during a traffic burst is the precise moment checkout and product pages begin to stall, so this card pairs an infrastructure signal (PgBouncer / pgpool pool usage) with a demand signal (sessions or requests from the ecommerce connector) to show revenue at risk before it is lost.

Calculation

For the PostgreSQL side, the engine reads PgBouncer’s admin console. SHOW POOLS returns per-database, per-user pools with cl_active (client connections doing work), cl_waiting (clients queued for a server connection), sv_active, and sv_idle. Saturation is computed as the server connections in use against the configured pool_size:
A pool at 100% with cl_waiting > 0 is the queuing state: every backend connection is busy and new client requests are stacking up behind them. Where no external pooler exists, the engine falls back to pg_stat_activity (active + idle-in-transaction backends against max_connections), the same basis as the Connection Pool Saturation % card. For the ecom side, the traffic-burst signal is the storefront session or request rate over the same 15-minute buckets, drawn from whichever ecommerce connector is linked. The card aligns both series on the same time axis and flags any bucket where pool saturation exceeds 90% while traffic is elevated above its rolling baseline. The cross-platform insight is in the correlation, not either number alone: saturation that rises and falls with traffic is a pure capacity ceiling you can raise; saturation that climbs while traffic is flat is a connection leak or a long-held transaction, which more pool size will not fix.

Worked example

A platform team supports a storefront on BigCommerce backed by an application that talks to PostgreSQL through PgBouncer in transaction-pooling mode, pool_size = 50. A flash sale opens at 12:00 BST on 22 Apr 26. Snapshot of the 15-minute table around the spike: The card headline flags 96% saturation during a traffic burst at 12:00 and escalates as the 12:15 bucket hits 100% with 184 clients queued. The story the table tells:
  1. Saturation is tracking demand, not leaking. At 11:30 to 11:45 the pool sat comfortably under 60% with no waiters. The jump to 96% lands exactly on the sessions jump from 1,500 to 6,800. This is a capacity ceiling reached by genuine traffic, not a runaway query.
  2. The pool is now the bottleneck, not PostgreSQL. With cl_waiting at 184, requests are queuing inside PgBouncer before they ever reach the database. Page renders that need a query are waiting milliseconds-to-seconds for a free server connection. This is where storefront latency and checkout abandonment begin.
The action is immediate and has two horizons. Right now: raise PgBouncer pool_size (and confirm PostgreSQL max_connections has headroom for the larger pool) or shed non-critical background workers off the same pool. Structurally: this sale revealed that pool_size = 50 cannot serve peak demand, so the team raises the ceiling before the next campaign and adds the saturation alert to the pre-sale runbook. Three takeaways:
  1. The conjunction is the signal. 90% saturation alone is ambiguous. 90% saturation during a burst is a near-certain revenue event, which is why the alert requires both conditions.
  2. cl_waiting is the line between “busy” and “broken”. A pool at 100% with zero waiters is fully utilised but coping. The same pool with hundreds of waiters is actively shedding requests. Read the waiter count, not just the percentage.
  3. More pool size only helps a capacity ceiling. If saturation tracks traffic, raise the pool. If saturation climbs while traffic is flat, the fix is upstream (a connection leak or a stuck transaction), and a bigger pool just delays the wall. Pair with Idle-in-Transaction Backends to tell them apart.

Sibling cards

Reconciling against the source

Where to look directly:
Connect to the PgBouncer admin console (psql -p 6432 pgbouncer) and run SHOW POOLS; for live cl_active, cl_waiting, sv_active, sv_idle per pool, and SHOW STATS; for request rates. SHOW CONFIG; confirms the configured pool_size and max_client_conn. Without an external pooler, SELECT count(*) FROM pg_stat_activity WHERE state IN ('active','idle in transaction'); against SHOW max_connections; gives the fallback saturation. For the traffic side, reconcile against the storefront analytics in the linked ecommerce connector (Shopify, BigCommerce, or Adobe Commerce) for the same 15-minute window.
Why our number may legitimately differ: On managed services: RDS Proxy and Aurora expose pinning and connection metrics through CloudWatch (DatabaseConnections, MaxDatabaseConnectionsAllowed, and RDS Proxy’s ClientConnections / DatabaseConnections); Cloud SQL’s built-in connection metrics appear in Cloud Monitoring. Where a managed pooler replaces PgBouncer, reconcile against the provider’s connection metrics rather than the PgBouncer admin console.

Known limitations / FAQs

The pool shows 100% but PostgreSQL max_connections is nowhere near full. How? That is exactly what a connection pooler is for. PgBouncer multiplexes many client connections onto a small set of server connections. The pool can be 100% saturated (every server connection busy) while PostgreSQL itself sees only pool_size backends, far below max_connections. The bottleneck is the pool, and the fix is to raise pool_size, provided PostgreSQL has the headroom for the extra backends. Saturation spiked but storefront traffic was flat. What does that mean? A leak or a stuck transaction, not a capacity problem. Backends are holding server connections without doing useful work, often clients that opened a transaction and stalled (see Idle-in-Transaction Backends). Raising pool_size only postpones the wall. Find the code path holding connections open and fix idle_in_transaction_session_timeout. Why require a traffic burst for the alert? 90% saturation sounds bad on its own. Because the same percentage means very different things at different times. Ninety percent during a 03:00 batch window with no shoppers is an operational note; ninety percent during a peak-hour burst is live revenue loss. Requiring both conditions keeps the alert tied to actual customer impact rather than paging on overnight maintenance. We do not run PgBouncer. Does this card still work? Yes, with a coarser basis. The engine falls back to pg_stat_activity saturation against max_connections, the same data as the Connection Pool Saturation % card. You lose the cl_waiting queue-depth signal, which is PgBouncer-specific, but you keep the saturation-versus-traffic correlation. The two series are not perfectly aligned in time. Why? The PostgreSQL pool sample and the ecommerce traffic signal come from different systems with different collection latencies, then get bucketed to the same 15-minute grid. A small phase offset between the curves is normal. Read the correlation across several buckets rather than demanding a per-second match. Can I tell bot traffic from real demand here? Partly. If pool saturation spikes with traffic but the linked PostgreSQL QPS Spike vs Ecom Order Rate card shows query volume rising with no matching order rise, the burst loading your pool is likely scrapers or bots rather than buyers. Read the two cross-platform cards together during any unexplained spike.

Tracked live in Vortex IQ Nerve Centre

PgBouncer Pool Saturation vs Traffic Burst 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.