Connection Pooling: The Quiet Killer of SaaS Performance
Connection pooling is the technique of reusing database connections across requests instead of opening a new connection for each. Done well, it dramatically reduces overhead and supports many more concurrent users. Done poorly, it produces incidents that look like database performance problems but are actually pool exhaustion. The pool size, the connection lifetime, and the queue behavior are the three settings that matter most.
What you actually need to know
- Pool exhaustion produces incidents that look like database problems.
- Total connections across all servers should fit comfortably under Postgres max_connections.
- PgBouncer is the standard for serverless Postgres workloads.
- Transaction pooling mode is the right default.
- Monitor checkout time and queue depth as the warning signals.
Setting
Typical value
Postgres max_connections
100 to 500 depending on instance size
App server pool size
max_connections / app servers / 2
PgBouncer pool size
Matches app demand
Pool mode
Transaction for most cases
Connection lifetime
30 minutes to a few hours
Idle timeout
Several minutes
Checkout timeout
A few seconds
The core argument
Connection pool incidents are some of the most common production issues in SaaS and some of the hardest to diagnose without context. The pattern is consistent. The application gets busy. Requests slow down. The team looks at the database. The database CPU is fine. The query latency is fine. The team is confused. The actual problem is that the application has run out of connections in its pool and new requests are waiting.
The fix is sizing the pool correctly and adding visibility into pool health. The pool should be large enough to handle the application's concurrency. The total connections across all application servers should fit comfortably under the database's max_connections. The monitoring should expose pool checkout time and queue depth.
The mistake teams make is using the framework default pool size and never measuring. The default is rarely right for the application's actual load. A pool of 10 is too small for an app server that handles 200 concurrent requests. A pool of 200 is too large if the database only allows 100 connections and you have five app servers.
Serverless workloads compound the problem. Each serverless instance opens its own connection. Without a pooler in front of the database, the database hits its connection limit immediately. PgBouncer or an equivalent connection pooler is required. The pooler maintains a small pool of real connections and serves many more application connections from that pool.
The configuration that works
Component
Setting
Application pool size
Sized for concurrency. Usually 5 to 50 per server
Pool acquisition timeout
A few seconds, not unlimited
Connection idle timeout
A few minutes
Connection lifetime
30 minutes to a few hours
PgBouncer pool mode
Transaction
PgBouncer pool size to Postgres
Sized for total concurrent transactions
Monitoring
Checkout time, queue depth, error rate
How much does this cost
Approach
Cost
Default pool with no pooler
Free, breaks at scale
Sized pool, no pooler
Free, works to moderate scale
Sized pool plus PgBouncer
Modest infrastructure cost
Sized pool plus managed pooler (RDS Proxy, Supabase pooler)
Slightly higher cost, lower ops
Features the connection setup must have
- Pool size set deliberately, not framework default.
- Acquisition timeout set so requests fail rather than hang forever.
- Connection lifetime set to prevent stale connections.
- Monitoring on checkout time, queue depth, and errors.
- A pooler in front of the database for serverless workloads.
- A documented connection budget across services.
- An alarm on pool saturation.
Expert opinion
Connection pooling is one of those topics engineers rarely think about until an incident forces the issue. Once it happens, nobody forgets the postmortem. Every team that hasn't had one yet is usually running pool settings that will eventually cause one. The sizing is mechanical, the monitoring is cheap, and the operational pain both prevent is real.
Yashveer Singh, founder of Yashveer Labs
How this played out on a real project
A client SaaS was experiencing intermittent latency spikes that the team had attributed to database performance. They had scaled up the database instance twice without improvement. The founder asked me to look.
The metrics showed pool checkout times of 800 milliseconds during the spikes. The database itself was idle. The pool was undersized for the load. The application had a pool size of 10 per server and was running 25 concurrent requests on most servers. Requests were queueing for connections.
We raised the pool size to 40 per server and added PgBouncer in front of the database to manage the total connection count. The latency spikes disappeared. The database scaling that had been done was unnecessary. The team adopted pool monitoring as a standard observability metric.
For more on the related work, see connection pooling why defaults are wrong for most stacks and PostgreSQL performance at scale the tweaks that move the needle.
Common mistakes teams make
- Using the framework default pool size.
- No pooler in front of Postgres for serverless workloads.
- Pool size larger than the database can handle.
- No monitoring on checkout time.
- No acquisition timeout. Requests hang forever.
- Session pool mode where transaction would have worked.
- Treating database performance as the problem when the pool is exhausted.
- No connection budget across services.
A 30 day plan to fix connection pooling
- Week one. Measure current pool checkout time and queue depth.
- Week two. Size the pool correctly for the concurrency.
- Week three. Add PgBouncer or equivalent if serverless.
- Week four. Wire monitoring. Set alarms on saturation.
For more on the related work, read connection pooling why defaults are wrong for most stacks and PostgreSQL performance at scale the tweaks that move the needle. On the broader scaling side, scaling from one thousand to one hundred thousand users the invisible database bottlenecks is the natural next read.
FAQ
Frequently asked
- What is connection pool exhaustion?
- How do I size the pool?
- What is PgBouncer?
- What is the difference between pool modes?
- What about serverless?
- How do I detect pool exhaustion?
- What is the worst pooling mistake?
Author
Why this work lands with me
I am Yashveer Singh. Founder of Yashveer Labs. I take this kind of project because I have done enough of them to know what kills them. The version of me that writes a post like this is the same one who builds the system afterward. There is no handoff to a junior, no agency middleman, no surprise scope. That is the bet I am making on my own brand.