Connection Pooling: Why Defaults Are Wrong for Most Stacks
Framework default connection pool sizes are designed for a generic workload. Yours is not generic. The right pool size depends on your concurrency, your database capacity, your query latency, and your worker count. The defaults are usually too small for production loads and sometimes too large for the database to support. Picking the right numbers requires a small amount of measurement and a small amount of math.
Written by Yashveer Singh, founder of Yashveer Labs.
What you actually need to know
- Framework defaults are conservative and almost never right.
- Size the pool for your concurrency at the 99th percentile.
- Verify total connections fit under the database limit.
- Long queries need a larger pool relative to worker count.
- Each service has its own pool. Total budget across services matters.
| Stack | Default | Typical correct value |
|---|---|---|
| Node.js pg | 10 | 20 to 50 |
| Rails ActiveRecord | 5 | 15 to 40 |
| Django | Variable | 15 to 40 |
| Java HikariCP | 10 | 20 to 50 |
| PgBouncer transaction mode | Variable | Sized for concurrent transactions |
The core argument
Framework defaults for connection pooling exist because the framework cannot know your workload. The default is a reasonable starting point for a generic small application. Your application is not generic. Your application is specific to its concurrency, its query patterns, and its database capacity. The default is the starting point. Tuning is the work.
The teams that run on defaults discover the problem when traffic grows. The pool that worked at one hundred concurrent users does not work at one thousand. The application stalls under load. The database is fine. The pool is the bottleneck. The team scales the database. The bottleneck remains because it was never the database. The team is confused and the customers are unhappy.
The right approach is to size the pool deliberately. Measure the concurrency at peak load. Pick a pool size that handles the 99th percentile of concurrency. Verify the total across servers fits under the database's max_connections divided by two. Reserve the other half for migrations, admin queries, and one off operations.
The other piece is the queue behavior. When the pool is full and new requests arrive, what happens. Wait. Time out. Reject. The default is usually wait forever, which produces hung requests. The right behavior is to wait a few seconds then time out, so the failure surfaces quickly rather than appearing as a slow response.
The tuning workflow
| Step | Detail |
|---|---|
| Measure concurrency | Database connections in use at peak load |
| Estimate growth | Where will concurrency be in six months |
| Compute pool size | Concurrency at growth peak plus small buffer |
| Verify database capacity | Total pool across servers under max_connections / 2 |
| Set acquisition timeout | A few seconds |
| Set connection lifetime | 30 minutes to a few hours |
| Add monitoring | Checkout time, queue depth, errors |
| Test under load | Confirm the new size handles peak |
How much does this cost
The cost of tuning is engineering time. A few hours to measure and adjust. The infrastructure cost difference is negligible. The savings show up as fewer incidents and lower latency under load.
Features the pool tuning must have
- A measurement of current concurrency.
- A pool size set deliberately based on the measurement.
- An acquisition timeout that surfaces failures quickly.
- A connection lifetime that prevents stale connections.
- Monitoring on the three key metrics.
- An alarm on saturation.
- A documented review cadence as the workload changes.
Expert opinion
The teams that have never tuned their connection pool are usually a traffic spike away from a hard to diagnose incident. The tuning is mechanical and cheap. The incident is expensive and confusing. The discipline of measuring concurrency and sizing the pool deliberately is one of the highest leverage observability practices in any production stack.
>
Yashveer Singh, founder of Yashveer Labs
How this played out on a real project
A client SaaS was running on the Node.js pg library default pool of 10. They had four application servers. Their Postgres allowed 100 connections. At peak load they were experiencing latency that they had attributed to slow queries.
The pool checkout time at peak was 600 milliseconds. The slow queries were actually fine. The pool was undersized.
We measured concurrency. The peak was around 30 concurrent operations per server. We raised the pool to 40 per server. Total connections were 160 across four servers, which was above the Postgres limit. We added PgBouncer in transaction mode with a pool of 50 to Postgres. The application could have 160 connections to PgBouncer. PgBouncer maintained 50 connections to Postgres.
The latency at peak dropped dramatically. The checkout time fell to under 5 milliseconds. The database was no longer near its connection limit. The fix took a day. The improvement was immediate.
For more on the related work, see connection pooling the quiet killer of SaaS performance and PostgreSQL performance at scale the tweaks that move the needle.
Common mistakes teams make
- Running on framework defaults.
- Sizing the pool to match worker count instead of concurrency.
- No measurement before tuning.
- No PgBouncer for serverless workloads.
- Acquisition timeout set to infinite.
- No monitoring on the pool.
- Pool total across servers exceeds database limit.
- No documented review cadence as load grows.
A 30 day plan
- Week one. Measure current concurrency at peak.
- Week two. Compute and apply the right pool size.
- Week three. Add PgBouncer or equivalent if needed.
- Week four. Add monitoring and alarms. Test under synthetic load.
For more on the related work, read connection pooling the quiet killer of SaaS performance and database query performance the five patterns that hurt the most. On the broader performance side, backend performance budgets how to set them is the natural next read.
Frequently asked
Why Yashveer Singh is the call for this work
I have spent the last four years writing software that runs in production. Three live client sites. A Roblox game with real players. Nexli, a school management system about to launch into private testing. Nyxera, a fully local AI assistant. Most people writing about this topic are summarizing other people's blog posts. I am writing from the codebase. If you want this kind of work done right, I am the person you call. Yashveer Singh, founder of Yashveer Labs.
Posts that line up with this one.
- Performance Optimization
The Caching Hierarchy: Browser, CDN, Edge, Application, Database
Every web application has five caching layers. Understanding which one to use for which data is how fast applications stay fast at scale.
- Performance Optimization
Database Query Performance: The Five Patterns That Hurt the Most
Most database performance problems come from a handful of patterns. The N plus one. The sequential scan. The unbounded result. The cross join. The lock contention. Fix these five and most performance work is done.
- Performance Optimization
The Cost of Over-Caching: Stale Data Stories
Caching solves performance problems. Over-caching creates correctness problems. Here is the taxonomy of stale data bugs and how to prevent them.
- Performance Optimization
Image Optimization at Scale: AVIF, WebP, Responsive Images
Images are the largest contributor to page weight on most web products. Here is the format selection, responsive image, and delivery strategy that cuts load time without manual work.