awesome-everything RU
↑ Back to the climb

Backend Architecture

Why pool: the cost of creating a connection

Crux Opening a database connection is not cheap — TCP, TLS, and auth cost milliseconds and the server allocates a whole backend process. A pool creates a small set of connections once and lends them out, so requests pay setup cost almost never instead of every time.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at junior altitude — the surface
◷ 11 min

A junior service opens a fresh Postgres connection for every request, runs one quick SELECT, and closes it. The query takes 1 ms; the request takes 35. The missing 34 ms are pure setup: a TCP handshake, a TLS handshake, and the database authenticating the user and forking a backend process — every single time, thrown away after one query. Under load the database spends more CPU creating and destroying connections than answering them. Nobody wrote a slow query. They wrote a fast query wrapped in an expensive ritual, repeated on every request.

A connection is expensive to create

To a beginner a database connection looks free — call connect(), get an object. Underneath, opening one is a chain of slow steps:

  • TCP handshake — a round trip to establish the socket (one RTT before any bytes flow).
  • TLS handshake — encryption setup, 2 round trips on TLS 1.3, 3 on TLS 1.2; on a link with real latency this alone is tens to hundreds of milliseconds.
  • Authentication — the database verifies credentials, often with its own challenge/response round trip.
  • Backend allocation — Postgres forks a dedicated OS process per connection; MySQL spawns a thread. That process reserves memory (~2–3 MB of shared/admin memory before it runs a single query).

So a connection is not a cheap handle — it is a TCP socket plus a TLS session plus a live server-side process. Creating and tearing one down per request means paying all of that for one query’s worth of work.

A pool amortises the cost

A connection pool is a small, long-lived set of already-open connections kept ready. Instead of connect → query → close, the flow becomes:

  1. Check out (acquire/borrow) a ready connection from the pool.
  2. Run your query on it.
  3. Return (release) it to the pool — not closed, just marked free for the next request.

The expensive handshakes happen once, when the pool fills, then those same connections are reused thousands of times. This is amortisation: a fixed setup cost spread across many uses until the per-request share is negligible. A request now pays ~0 ms of setup instead of ~34, and the database stops burning CPU on connection churn.

Why this works

Why keep connections open and idle rather than open-on-demand and close-when-done? Because the whole point is to not pay setup on the request path. An idle pooled connection costs a little memory on both client and server, but it is instantly available — checkout is a near-free in-process operation. The alternative, opening per request, moves the handshake latency directly into the user’s wait time and turns the database into a connection factory under load. The pool trades a small, bounded, steady-state cost (a handful of idle connections) for the elimination of a large, repeated, per-request cost. That trade is almost always worth it for a server that handles more than a trickle of traffic.

A pool is also a limit

Reuse is the obvious benefit, but the pool’s second job matters just as much: it is a bound. A pool of size 20 means at most 20 connections ever exist from this service to the database, no matter how many requests arrive. That cap is a feature — it protects the database, which can only handle so many backends before it falls over (Postgres defaults to max_connections = 100). Without a pool, a traffic spike of 5,000 concurrent requests would try to open 5,000 connections and the database would reject them with “too many clients.” The pool turns unbounded demand into a fixed, survivable number — the same bounded-concurrency idea from the last unit, applied to the most expensive downstream resource a backend has.

Connect per requestPooled connections
Setup cost paidEvery request (TCP+TLS+auth)Once, at pool fill
Per-request latency+ tens of ms of handshake~0 ms (in-process checkout)
DB-side loadConstant fork/teardown churnStable set of backends
Concurrency to DBUnbounded (spike → overload)Capped at pool size
Failure under spike”too many clients”, refusedRequests queue or fail fast
Quiz

A handler runs a 1 ms query but each request takes ~35 ms end to end, and the database CPU is high even though queries are trivial. What is the most likely cause?

Quiz

Besides reusing connections, what second protection does a fixed-size pool give a backend?

Order the steps

Order the lifecycle of one request using a connection pool:

  1. 1 Check out a ready connection from the pool
  2. 2 Run the query on the borrowed connection
  3. 3 Return the connection to the pool (free, not closed)
  4. 4 A later request checks out that same connection and reuses it
Recall before you leave
  1. 01
    Why is creating a database connection expensive, step by step?
  2. 02
    How does a connection pool work and what does amortisation mean here?
  3. 03
    Why is a pool also a bound, and why does that matter?
Recap

A database connection looks free but is a chain of expensive setup: a TCP handshake, a TLS handshake (two round trips on 1.3, three on 1.2), authentication, and a server-side backend process that reserves a couple of megabytes before doing any work. Opening one per request buries tens of milliseconds of handshake in every call and makes the database burn CPU forking and tearing down processes — a fast query wrapped in an expensive ritual. A connection pool fixes both halves: it opens a small set of connections once and lends them out (check out, query, return-not-close), amortising setup to nearly zero per request, and it caps total connections at its size, protecting a database that defaults to only 100 backends from spike-driven ‘too many clients’ overload. The pool is bounded concurrency for the priciest resource a backend touches. But a pool only helps if it is the right size — and the next lesson shows why bigger is counterintuitively not better, and how to compute the number that actually maximises throughput.

Connected lessons
appears again in185
Continue the climb ↑Pool sizing: why bigger is not faster
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.