Database Connection Pool Size Calculator

A connection pool needs about as many connections as are busy at the same moment, which by Little's Law is query rate × time each query holds a connection. At 1,200 RPS with 3 queries of 10 ms per request, 1,200 × 3 × 0.010 s = 36 connections are busy on average; across 4 app instances that is 9 each, or 14 per instance with 50% headroom, and 4 × 14 = 56 plus other clients must stay under the database's max_connections. The calculator below does this per instance and fleet-wide, and checks it against the database limit.
The formula: Little's Law applied to connections
A pooled connection is either idle or busy serving one query or transaction. The average number busy is the rate at which connections are borrowed times how long each borrow lasts. That is Little's Law, L = λ × W, explained in depth in the Little's Law guide:
hold_time = queries_per_request × avg_query_time (per-query mode)
= time the request keeps its connection (whole-request mode)
busy_total = peak_RPS × hold_time
busy_per_inst = busy_total / instances
pool_per_inst = ceil(busy_per_inst × (1 + headroom%))
db_total = pool_per_inst × instances + other_connections
must hold: db_total ≤ max_connections
Query time here means what the application sees: execution plus network round trip plus driver overhead. Measure it at the app, not from the database's own statistics, which leave out the network.
Headroom exists because Little's Law gives an average. Arrivals are bursty, so the number busy at any instant fluctuates above the mean. With 50% headroom a pool sized at 9 average busy becomes 14; past that, requests wait for a connection instead of failing, which is the behavior you want.
Worked example
An illustrative API using the calculator's defaults:
- Hold time: 3 queries × 10 ms = 30 ms per request.
- Busy connections fleet-wide: 1,200 RPS × 0.030 s = 36.
- Per instance: 36 ÷ 4 = 9, × 1.5 headroom = 13.5, rounded up to 14.
- At the database: 14 × 4 + 10 other = 66 of 100 allowed.
- Autoscaling ceiling: (100 − 10) ÷ 14 = 6.4, so the seventh instance would push the database past
max_connections.
The busy count, 36, sits slightly above the HikariCP reference of 32 for the example's 16-core database (explained below). That is not alarming on its own, because 10 ms per query includes network time during which the database is idle, but it says this database has little room for more concurrent work.
Now switch to "whole request" mode with 80 ms held per request, typical when a framework opens a transaction at the start of the request and commits at the end. Busy connections become 1,200 × 0.080 = 96, the pool per instance 36, and the total 154. The database refuses connections long before peak. Same traffic, same queries; only the connection lifetime changed.
The HikariCP formula, and what it is for
The HikariCP wiki page "About Pool Sizing" gives a starting point that comes from PostgreSQL benchmarking: active connections "should be somewhere near ((core_count * 2) + effective_spindle_count)". It adds that core count "should not include HT threads, even if hyperthreading is enabled", and that "effective spindle count is zero if the active data set is fully cached". Its own example is a 4-core server with one disk: 9 = (4 × 2) + 1. The same page states the principle behind it: "You want a small pool, saturated with threads waiting for connections."
Two points are easy to miss:
- It is about the database server's cores, and it describes the total number of active connections the database runs efficiently, not the pool in each app instance. Compare it with the fleet-wide busy count, not the per-instance pool.
- It is a throughput guideline, not a hard limit. If Little's Law says you need far more busy connections than cores × 2, queries are already queueing for CPU or I/O inside the database. Adding pool slots then adds contention, not throughput. The fix is shorter hold times: faster queries, fewer queries per request, shorter transactions.
Defaults that set the ceiling
| Setting | Default | Notes |
|---|---|---|
PostgreSQL max_connections | 100 | 3 of them are kept for superusers by superuser_reserved_connections |
MySQL max_connections | 151 | One extra connection is reserved for administrative accounts |
HikariCP maximumPoolSize | 10 | Per pool, so per app instance |
Managed databases often set max_connections from instance memory rather than these defaults, so read the value from the running server (SHOW max_connections; works on both PostgreSQL and MySQL, with slightly different output).
Exceeding the limit produces errors such as FATAL: sorry, too many clients already (PostgreSQL) or ERROR 1040 (HY000): Too many connections (MySQL). Running out of pool slots on the app side looks different: requests wait, then fail with a pool timeout. The connection pool exhausted guide covers diagnosing that during a test.
Autoscaling, serverless and connection poolers
The fleet-wide total grows with every instance, while max_connections does not. If your autoscaler can reach 12 instances, size against 12, not against the 4 you run today. The calculator's "max instances" figure is the hard ceiling; set your autoscaler's maximum below it or reduce the per-instance pool. Slow autoscaling and connection limits often show up together in the same test.
When the instance count is large or unpredictable, as with serverless functions, put a connection pooler between the app and the database. PgBouncer in transaction mode lets many client connections share a small number of server connections, so the database sees roughly the busy count, not instances × pool size. Transaction mode has trade-offs with session-level features such as session variables and some prepared statement setups; check your driver's compatibility.
Verify the size with a load test
The calculator gives a starting value. A load test confirms it:
- Expose pool metrics: active, idle, pending (threads waiting) and acquire time. HikariCP, node-postgres, SQLAlchemy and most other pools publish these.
- Ramp RPS in steps to the target in an arrival-rate test, holding each step for a few minutes. In LoadTester that is RPS mode with a p95 threshold and auto-stop.
- At each step, compare active connections with the calculator's busy count. If observed is much higher, hold time is longer than you assumed; check for slow queries or transactions held across external calls.
- Watch pending count and acquire time. Sustained waiting at your target RPS means the pool, or the database behind it, is the bottleneck.
Framework-specific notes on pool settings are in the Spring Boot and Django load testing guides.
Checklist
- Measure queries per request and app-side query time for your main endpoints.
- Decide whether connections are held per query or for the whole request.
- Compute pool per instance with headroom, and the total at the maximum autoscaled instance count.
- Keep the total below
max_connectionsminus other clients, or add a pooler. - Compare busy connections with (cores × 2) + spindles on the database; if far above, shorten hold times.
- Load test in steps and watch active, pending and acquire time.
FAQ
How do I calculate database connection pool size?
Multiply the rate of connection borrows by how long each borrow lasts: peak RPS times queries per request times average query time, or RPS times the time a request holds its connection. Divide by the number of app instances and add headroom for bursts, typically 30 to 50 percent.
What is the HikariCP pool size formula?
The HikariCP wiki suggests active connections should be somewhere near ((core_count * 2) + effective_spindle_count), counting physical database cores without hyperthreads and using zero spindles when the active data set is fully cached. It describes the total the database runs efficiently, not the pool in each app instance.
Is a bigger connection pool faster?
Usually not past a point. Once the database's CPU or disk is saturated, extra connections add contention and context switching, so throughput stays flat while latency rises. Shorter hold times, from faster queries and shorter transactions, increase capacity; extra pool slots do not.
What happens when total connections exceed max_connections?
The database refuses new connections, with errors such as 'FATAL: sorry, too many clients already' on PostgreSQL or 'Too many connections' on MySQL. It often appears only when autoscaling adds instances, because each new instance opens its own pool.
How do I size the pool for serverless or many instances?
Size against the maximum instance count the platform can reach, not the typical one. If that total cannot fit under max_connections, put a pooler such as PgBouncer in transaction mode between the app and the database so the server sees roughly the busy count.