Connection pooling or kill your database

Connection pooling or kill your database

Imagine your vehicle’s engine having to restart every time you press the brake. You'd consume so many resources that you'd go broke constantly replacing damaged components. The same happens when you don’t implement connection pooling for your database.

To be fair, some vehicles implement an “auto stop-start” system that shuts the engine off when the brake pedal is pressed, “saving” fuel and reducing emissions. My car has it and I always disable it. There’s debate about the safety of the feature, but since we’re talking about databases and not automotive engineering, I’ll keep it disabled.

Whenever you run a query or operation, the client must open a connection to the database server. That connection handshake is relatively expensive in both time and resources. Here’s a concise overview of what happens under the hood:

  1. DNS lookup: the client resolves the hostname to an IP address.

  2. TCP handshake: the classic three-way handshake that establishes a TCP connection.

  3. TLS/SSL (optional but strongly recommended): certificate exchange to encrypt the connection — otherwise traffic travels in cleartext.

  4. Protocol startup: the client and server negotiate protocol behavior and configured parameters.

  5. Authentication exchange: the client and server negotiate credentials and auth method.

  6. Session creation: the DB server establishes the session and allocates a process/thread to serve the connection.

  7. Client initialization: client libraries run initialization routines (load metadata, validate schema, etc.).

  8. Application query: the application runs queries (often starting a transaction).

  9. Connection closed: client sends TCP FIN and the session closes.

All of that runs under the hood whenever you establish a DB connection. Each step adds RTT (round-trip time — the time a packet takes to travel from source to destination) and consumes CPU and memory. You may think this is lightning-fast, but it becomes a bottleneck when you require a new connection for every user query.

The solution is to create a process that stays alive longer, so you skip steps 1–6 and execute application queries using an existing connection. That is the purpose of connection pooling.

Connection pooling has trade-offs and configuration details that must be considered for a good implementation. Now that you know why it’s needed, here are best practices and tips for creating a solid pool.

Pool sizing. Connection pooling is a process like any other on your machine. For that reason you must set the maximum number of simultaneous connections the pool can hold. If you don’t, some libraries default to a single connection and you risk under-utilization or timeouts from pool exhaustion.

Timeouts. Another key configuration is connection vs. idle timeouts. Connection timeouts express the app’s “patience” for establishing a connection before failing, while idle timeouts determine how long an unused connection stays open before being closed to free resources. Values depend on your app, but a useful rule of thumb is to base them on observed latency — and never set connection timeout to 0. Trust me: don’t learn that one the hard way.

Connection validation. There are many additional pool settings — like anything in tech, this can become an infinite rabbit hole. One setting I use when I want things to feel elegant is connection validation: a quick “pre-query” run before handing over a connection to verify the pool is healthy and avoid surprises.

Connection pooling appears in many places across software engineering; ultimately it’s a representation of a fundamental computer-science skill: resource management. Can you implement pooling for Redis? Yes — but it’s less critical than for relational databases because Redis is single-threaded. That’s a topic for a future article.