Quick answer
Database connection pooling keeps a reusable set of database connections open so backend requests do not create a new connection for every query.
A connection pool improves latency and protects the database from uncontrolled connection growth, but it must be sized carefully. Too few connections create waiting. Too many connections can overload the database.
The most important settings are maximum pool size, connection timeout, idle timeout, max lifetime, leak detection, and monitoring.
Why connection pools exist
Opening a database connection is not free. A new connection may require network setup, authentication, TLS negotiation, memory on the database server, session state, and driver initialization.
If every request opened and closed a physical connection, a busy API would waste time and resources before it even runs SQL.
A pool changes the workflow:
request -> borrow connection -> run query -> return connection to pool
The physical connection stays open and can be reused by another request.
The basic pool lifecycle
Most backend connection pools follow the same lifecycle:
- Start with a small number of idle connections or create them on demand.
- A request borrows a connection.
- The request runs queries and transactions.
- The request closes the logical connection handle.
- The pool returns the physical connection to the idle set.
- Old, broken, or expired connections are replaced.
The application code usually calls close(), but that does not necessarily close the real database socket. It returns the connection to the pool.
Pool size is a capacity decision
Pool size controls how many concurrent database connections one application instance can use.
For example:
10 app instances
20 max connections per instance
= up to 200 database connections
If the database can safely handle 150 application connections, this configuration is too high even though each individual service instance looks reasonable.
Pool sizing must account for:
- Number of application instances.
- Database max connections.
- Other services using the same database.
- Admin tools, migration jobs, workers, and reporting jobs.
- Average query latency and transaction duration.
- Traffic spikes and retry behavior.
Bigger is not always better
A larger pool can reduce waiting inside the application, but it can also make the database slower.
Databases have finite CPU, memory, locks, buffers, and I/O. If too many queries run at once, they compete with each other. Latency rises, requests hold connections longer, and the system can enter a feedback loop.
Sometimes a smaller pool improves stability because it limits concurrency before the database is saturated. The queue moves to the application, where timeouts and backpressure are easier to control.
Connection timeout
Connection timeout controls how long a request waits to borrow a connection from the pool.
If the pool is exhausted, the request waits. If no connection becomes available before the timeout, the application returns an error.
A good timeout should be short enough to fail quickly during overload and long enough to tolerate normal short bursts.
Symptoms of pool exhaustion include:
- Requests waiting before SQL starts.
- Timeout errors from the pool, not the database.
- High active connection count.
- Low idle connection count.
- Long transactions or leaked connections.
Idle timeout and max lifetime
Idle timeout removes connections that have been unused for a while. This prevents idle application instances from holding unnecessary database resources.
Max lifetime retires old connections even if they are otherwise healthy. This helps avoid stale connections behind load balancers, database restarts, network changes, and server-side connection limits.
These values should be coordinated with database and infrastructure settings. If the database or proxy closes connections earlier than the pool expects, the application may see sudden broken-connection errors.
Connection leaks
A connection leak happens when application code borrows a connection and does not return it.
Common causes include:
- Missing
close()in manual JDBC code. - Exceptions that skip cleanup.
- Transactions that never complete.
- Streaming results held open too long.
- Background jobs that keep a connection while doing non-database work.
Modern frameworks reduce this risk, but leaks still happen when code mixes manual connection management, custom transactions, and long-running jobs.
Leak detection can log a warning when a connection has been checked out too long.
Transactions and pool pressure
A request holds a connection for the duration of its transaction.
This is why long transactions can exhaust a pool even if the SQL itself is not expensive. If application code opens a transaction, calls an external API, waits for a remote service, and then commits, the database connection is held for the whole wait.
Read Database Transactions Explained for transaction boundaries and why external calls should usually happen outside active database transactions.
Pools and background workers
Background workers need pool sizing too.
A queue worker fleet can create more database load than HTTP traffic because workers often run batch updates, retries, imports, exports, or scheduled maintenance.
Do not only count web server instances. Count:
- API servers.
- Worker processes.
- Scheduled jobs.
- Migration tasks.
- Local admin scripts.
- Reporting services.
Each process may have its own pool.
Monitoring connection pools
Useful pool metrics include:
| Metric | Why it matters |
|---|---|
| Active connections | How many connections are currently borrowed. |
| Idle connections | How much room is available without creating new connections. |
| Pending waiters | How many requests are waiting for a connection. |
| Borrow wait time | Whether requests are delayed before SQL starts. |
| Timeout count | Whether the pool is rejecting work. |
| Connection lifetime | Whether connections churn too often. |
| Leak warnings | Whether code holds connections too long. |
Pool metrics should be viewed next to database CPU, query latency, lock waits, and request latency.
Common mistakes
The first mistake is setting the pool size per instance without multiplying by the number of instances.
The second mistake is increasing pool size every time latency rises. If the database is already saturated, more connections can make latency worse.
The third mistake is running slow external calls inside transactions. That keeps a database connection busy while no database work is happening.
The fourth mistake is ignoring workers and migration jobs. Production connection limits are shared by everything that connects to the database.
The fifth mistake is using long timeouts that turn overload into a slow queue instead of a clear failure.
Practical backend checklist
When configuring a database pool:
- Count all application instances and workers.
- Check the database’s safe connection limit.
- Reserve connections for admin, migration, and emergency access.
- Start with a conservative max pool size.
- Set a clear connection timeout.
- Enable pool metrics.
- Enable leak detection in non-production and for suspicious production services.
- Keep transactions short.
- Load test realistic request and worker concurrency.
- Review pool settings whenever autoscaling changes.
Connection pooling is part of system design because it controls the pressure between application concurrency and database capacity.
Related reading
Read Database Transactions Explained for transaction boundaries and Database Indexes Explained for query performance after a connection is borrowed. Read N+1 Query Problem Explained when many small queries consume pooled connections. Use the SQL Formatter while reviewing slow queries, and browse the System Design Learning Path or Topics for related backend architecture guides.