performance

Connection Pooling

Reuse a fixed set of physical DB connections — opening new ones per request is too expensive and the DB has a hard limit.

Opening a database connection costs a TCP handshake, TLS negotiation, authentication and session setup — often tens of milliseconds.

A taxi rank of pre-warmed cars vs calling for a new car each time. Way faster, but only if you size the rank correctly.

Key Concepts

1
Paying that on every request is unaffordable. A pool keeps a set of connections open and lends them out.
2
Borrow and return, not open and close. The pool opens its connections at startup. Code borrows one, uses it, and returns it:
3
    try (Connection c = dataSource.getConnection()) {   // borrow
        ...
    }                                                   // return, not close
4
close() here means "give it back". The physical connection stays open for the next caller, and the expensive setup is paid once and spread across thousands of requests.
close()
5
The pool is also a throttle, and that is half its value. Its maximum size caps how many queries can run at once. When all connections are busy, the next request waits briefly instead of adding more load.
6
Without that cap, a slow query under traffic means every request opens its own connection, the database runs out of memory or backend processes, and it stops serving anyone. The pool converts an outage into queueing.
7
Bigger is not better, and this surprises people. A database executes queries on a limited number of cores. Give it 500 concurrent connections on 8 cores and it does not do more work — it context-switches between 500 of them and each one gets slower.
8
    8 cores, 100 connections   -> each query fights for CPU, all slow
    8 cores, 20 connections    -> queries run, the rest queue briefly, throughput higher
9
A starting point is roughly (cores x 2) + effective spindles, then measure. A pool of 10-20 per instance is normal, and far more often too large than too small.
(cores x 2) + effective spindles
10
Count instances, not pools. This is the mistake that takes production down. Ten application instances with a 50-connection pool each want 500 connections; the database allows 200.
11
The sum across every instance — plus batch jobs, plus admin tools — must stay under the server's limit. Autoscaling makes it worse, because the number of instances changes without anyone revisiting the pool size.
12
The two failures to recognise.Timeout on acquisition means the pool is exhausted. The cause is usually not pool size but slow queries holding connections longer, or a leak.
13
A leak is a borrowed connection never returned — an early return or a thrown exception on a path without try-with-resources. The pool shrinks until nothing works, typically hours after deploy. Leak detection thresholds exist for exactly this.
return
14
What the interviewer is probing.1. "Why pool connections?" Probing: the cost. Stalls: "Convenience." Moves up: establishing one costs a TCP handshake plus authentication, tens of milliseconds, and each connection costs server memory.
15
2. "What happens when the pool is exhausted?" Probing: the symptom. Stalls: "It creates more." Moves up: callers block until one frees or time out — which looks like a slow database when it is actually a leak or an undersized pool.
16
3. "Is a bigger pool always better?" Probing: the counter-intuitive part. Stalls: "Yes." Moves up: no — beyond the database's capacity to execute concurrently, more connections add contention and make throughput worse.
17
4. "Why do serverless functions break pooling?" Probing: the architecture. Stalls: "They do not." Moves up: each instance has its own pool and they scale independently, so the database sees hundreds of pools — which is what a proxy solves.