Throughput against connection pool size, rising to a peak around fourteen connections and falling away as contention grows

Connection Pool Sizing

Back to Performance Tuning and Capacity Planning · Kernel and Filesystem Parameters · Caching Strategy · Before-and-After Measurement · Service Offerings

Database and HTTP connection pools sized to match real concurrency, avoiding both starvation and wasted overhead. Both failures are real, they look similar from the outside, and the remedy for one makes the other worse — which is why this is worth measuring rather than guessing.

1. The Curve Has a Peak

Throughput against pool size is not a rising line. It rises, peaks, and falls, as in the illustration above, and the fall is not gentle.

The practical consequence is counter-intuitive and worth stating plainly: when a system is slow under load, enlarging the pool is the usual reaction and is frequently the wrong direction.

2. The Right Size Is Smaller Than People Expect

A database executes queries on CPUs and disks it actually has. Concurrency beyond that is queueing with extra overhead, and the useful number is driven by the server's resources rather than by your traffic.

Little's law gives the sanity check without any benchmarking. Required concurrency equals arrival rate multiplied by service time: 500 queries per second averaging 4 ms is 500 × 0.004, or two connections busy at any instant. A pool of 100 for that workload is 98 idle connections, each still costing memory on the database and a slot against its limit.

Start from a figure in the region of the database's core count rather than from the number of application threads, then measure.

3. Pools Multiply, and the Limit Is Shared

The arithmetic that produces outages: a pool of 50 looks modest until the service runs eight instances, and then it is 400 connections against a database configured for 200. Nothing is wrong with any single configuration.

4. The Timeout Decides What Saturation Looks Like

When the pool is exhausted, the acquisition timeout decides whether you get a slow system or a failing one. Both are bad; one is recoverable.

5. Connections Held Longer Than the Query

A pool exhausts at a fraction of its nominal capacity when connections are held across work that is not database work. The common patterns:

Each of these is fixed in the application rather than by sizing, and each makes the pool look too small when it is not. Separate pools for interactive and batch work solve the last one outright.

6. HTTP Pools Fail Differently

Outbound HTTP pools have the same shape and one extra failure: a pool too small for a slow remote service turns someone else's latency into your outage. The remote call takes two seconds, your pool holds ten connections, and above five requests per second everything queues.

Size against the remote service's actual latency rather than its advertised one, keep separate pools per destination so one slow dependency cannot starve the others, and set a timeout that is shorter than your own response deadline.

How We Approach It

  1. Measure real concurrency — arrival rate and service time — rather than starting from thread counts.
  2. Count every pool against the database limit, including replicas, batch jobs and tooling, and compare the total to what the server allows.
  3. Find the peak by testing, at several sizes under representative load, since the curve's shape is specific to your queries and hardware.
  4. Check for connections held longer than their queries, which is a code problem that sizing cannot fix.
  5. Set timeouts that fail fast and inside the caller's deadline, and separate interactive traffic from batch.
  6. Instrument acquisition wait time, which is the metric that tells you the pool is wrong, and alert on it.

What You Get

The question that settles most arguments about this: at peak, how long does a request wait to get a connection? If the answer is zero, the pool is not your problem however small it looks.