System Design

Connection pools: 45ms to open a connection vs. 0.2ms to borrow one

TL;DR

Every new database connection goes through TCP, TLS and authentication handshakes — about 45ms in the video. A connection pool keeps connections open so they can be borrowed in ~0.2ms. When traffic exceeds the pool size, requests wait their turn.

Watch on Facebook

Every time an application opens a new database connection, it has to do a TCP handshake, negotiate TLS and authenticate: in the video about 45ms before a single query can run. For a query that takes only 2ms, you pay a connection cost more than 20 times the actual work. A connection pool keeps a set of connections ready to borrow in about 0.2ms. When traffic exceeds the number of connections in the pool, requests have to wait their turn.

Three scenarios

ScenarioTime to get a connectionNotes
No pool~45ms per requestTCP → TLS → AUTH repeated every time
Pool, light load~0.2msBorrow an existing connection, no new handshake
Burst of 14 requests, pool of 410ms, 20ms, 30ms…10 requests queue for a free connection

After the burst, idle connections are closed by the idle timeout.

Key parameters

ParameterMeaning
Maximum sizeMaximum concurrent connections to the database
Minimum sizeConnections kept ready even when idle
Connection acquire timeoutHow long to wait before erroring when the pool is exhausted
Idle timeoutHow long before an idle connection is closed
Max lifetimeMaximum age of a connection, to avoid stale broken connections

A bigger pool isn't necessarily faster

Databases have limits on connections and resources (CPU, memory, locks). Too many active connections contend with each other and slow everything down. A smaller pool that's just big enough often performs better. A sensible approach is to start small, measure, and grow only with evidence.

Common mistakes

  • Connection leaks: not returning connections to the pool after use; the pool drains and the app hangs.
  • Holding connections too long: opening a transaction, then doing other work (calling external APIs) before committing.
  • Dead connections: after a database restart or network timeout; validate connections when borrowing.
  • One pool per instance: many instances multiply the connection count and can exceed the database's limit.

How to apply it

  • Always use a pool for apps with steady traffic (HikariCP for Java, pgBouncer for PostgreSQL, the built-in pool in most ORMs).
  • Track "connection acquire time" as its own metric; when it rises, the pool is too small or queries hold connections too long.
  • Serverless: per-instance pools easily exhaust DB connections; use a proxy such as RDS Proxy or pgBouncer.
  • Keep transactions short so connections are returned quickly.

Sources & references

  1. About Pool Sizing — HikariCP