The error is unmistakable: “too many connections” in MySQL, or “sorry, too many clients already” in PostgreSQL. The reflex is to raise the limit. That sometimes works for a week and then makes the next failure worse.
Why Connections Are Not Free
A database connection is not just a socket. Each one typically carries its own memory, session state and, in some engines, a dedicated operating system process or thread. PostgreSQL historically uses a process per connection, which makes very large connection counts expensive. MySQL uses threads, which is lighter but still has per-connection memory costs.
There is a second cost, less obvious: concurrency. A database has a limited number of CPU cores and disk operations it can usefully run at once. Two hundred connections all running queries do not finish faster than twenty; they fight over the same resources and add context switching and lock contention. Past a point, more concurrent connections lower total throughput.
That is the core insight behind pooling: you want many application clients, but a modest, steady number of actual database sessions.
Where Connection Pressure Comes From
Before changing anything, work out why you have so many connections. Common sources:
- Many application instances, each with its own pool. Ten app servers with twenty connections each is two hundred before any traffic.
- Frameworks opening a connection per request with no reuse.
- Connection leaks, where code opens a connection and fails to release it on an error path.
- Idle-in-transaction sessions, where a transaction is opened and left hanging, holding resources and locks.
- Background workers, cron scripts and admin tools that connect separately and are forgotten.
- Serverless or short-lived processes that create a fresh connection on every invocation.
Looking at who is connected tells you which applies. In PostgreSQL, the activity view shows each session’s user, application, state and query age. In MySQL, the process list gives similar information. Group by application or user, and look at the states: a pile of idle connections suggests oversized pools or leaks, while a pile of active ones suggests slow queries or a genuine capacity ceiling.
What Pooling Does
A pool keeps a set of open connections and hands them out to requests, taking them back when done. There are two common places to put one.
In the application. Most database drivers and frameworks include a pool. It is simple and effective, but each application instance has its own pool, so total connections grow with the number of instances.
In a separate pooler. A proxy such as PgBouncer for PostgreSQL, or ProxySQL for MySQL, sits between applications and the database. Many client connections multiplex over a smaller number of server connections. This keeps the database’s session count stable even when application instances scale up or are short-lived. Check each tool’s current documentation, because the pooling modes carry trade-offs: in transaction-level pooling, features that rely on session state (certain prepared statement behavior, session variables, advisory locks) may not work as expected.
Sizing a Pool Sensibly
There is no universal number, and anyone quoting one without knowing your workload is guessing. A workable approach:
- Start from the database’s resources: how many cores and how fast the storage is. A small multiple of the core count is a common starting point for active connections, then adjust from measurement.
- Divide your total budget across application instances, leaving headroom for admin access, monitoring, replication and migrations.
- Load test with realistic traffic, watching throughput and latency as you vary pool size. Often a smaller pool performs better than a larger one.
- Set sensible timeouts so a request waits briefly for a connection and then fails clearly, rather than queueing forever.
Keep the database’s configured maximum above your planned total, but not wildly so, because the maximum is also what protects the server from a runaway client.
Quick Reference
| Symptom | Likely cause | Direction |
|---|---|---|
| Many idle connections | Oversized pools or leaks | Shrink pools, add idle timeouts, hunt leaks |
| Many “idle in transaction” | Transactions left open | Fix application code, set a transaction idle timeout |
| Many active, slow queries | Capacity or query problem | Tune queries first, then scale |
| Spikes when instances scale | Per-instance pools multiply | Introduce an external pooler |
| Errors from short-lived functions | New connection per invocation | Pooler or managed proxy |
A Realistic Example
Picture a web application on four servers, each configured with a pool of fifty connections, against a database allowing a hundred and fifty. It works until a deployment adds two more servers and the total exceeds the limit during restarts, because old and new processes briefly overlap. The first reaction is to raise the database maximum to five hundred. Instead the team checks activity and finds most sessions idle. They reduce each pool to a much smaller number, add an idle timeout, introduce a pooler in front of the database, and add an alert when connections reach a fraction of the limit. Latency actually improves, because the database is no longer juggling hundreds of mostly idle processes.
Frequently Asked Questions
Should I just increase max connections? Only after understanding why you hit the limit. Raising it can raise memory use and contention, and it hides leaks. It is a valid change sometimes, but rarely the first one.
What is the difference between a pool in my app and PgBouncer? App pools are per instance and simple. An external pooler is shared across all clients, which keeps total database sessions stable as instances scale, at the cost of an extra component and some session-feature limitations.
What does “idle in transaction” mean? A session started a transaction and has not committed or rolled back. It can hold locks and block cleanup work. It usually points to an application bug or a missing timeout.
How do I find connection leaks? Watch connection counts over time while traffic is flat. A steady climb suggests leaks. Application-level pool metrics and query-age monitoring usually point to the code path.
Do serverless functions need special handling? Often yes, because many short-lived instances can each open connections. A pooler or a provider’s managed proxy is a common solution. Check current documentation for your platform.
Next Step
Query your database’s current sessions, grouped by user or application and by state. If idle connections dominate, shrink one pool, add an idle timeout, and watch what happens to latency over a day.
By CRMServerBase Editorial · Updated November 4, 2026
- connection pooling
- too many connections
- PostgreSQL
- MySQL