Why 10,000 database connections is not a database setting


When an application grows from a few hundred users to thousands, the database is usually the first thing people worry about. And the neatest-looking fix is a setting: raise the maximum connection count to match the number of users.
It sounds logical. More users means more simultaneous work, so surely the database needs more simultaneous connections.
But users and connections aren’t the same thing. Someone can keep a tab open for an hour while making only a handful of requests, and each request may hold a connection for just a few milliseconds.
So the real question isn’t how many users you have. It’s how much work actually reaches the database at once, how long each piece holds a connection and what the database can run in parallel.
Let’s work through why a higher limit can make things worse, how a connection pool changes the picture and how to tell a slow query from a request that’s simply waiting.
What a higher limit lets in
PostgreSQL, for example, accepts 100 connections by default, so the proposal looks like this:
That change is not always wrong. If requests are failing because the database refuses new connections, a higher limit may reduce those immediate failures, and in a small system raising a low default can be a reasonable short-term mitigation. What it does not tell us is what all of those connections will do once they are accepted. Every open connection consumes memory, socket resources, session state, and scheduling overhead. More importantly, a larger number of connections permits more work to arrive at the database at the same time.
If the workload contains expensive queries, long transactions, lock contention, or inefficient read patterns, increasing the connection ceiling can turn a controlled queue into an overloaded database.
At that point, the system can have a higher configured connection limit and worse user-facing latency. The setting changed; the bottleneck did not.
A pool helps until its size becomes the limit
The next response is usually more sophisticated:
“Fine; we will use a connection pool.”
That is the right direction. A pool reuses a bounded set of database sessions instead of letting every request open a fresh connection, and it avoids the cost of repeated connection setup. But it can still hide the real problem if its size is treated as a number to keep increasing.
Then the application scales out:
Nothing in the pool configuration changed, yet the potential pressure on the database grew fivefold. This is especially easy to miss in serverless or autoscaling environments: one function instance may seem to have a modest pool, but a burst of traffic creates many instances at once, each opening its own connections. The aggregate connection count is what the database actually experiences. A connection pool is a concurrency limit as much as an optimisation, so its size belongs in the capacity model.
Are queries slow, or are requests waiting?
When someone says “the database is slow,” I want to separate two different measurements:
They are often confused because the user experiences both as a slow request. Imagine this request:
The query is not the immediate bottleneck; the request spent most of its time waiting for a shared resource. Now compare it with this:
Here, increasing the pool may make things worse. More queries will arrive concurrently, and each slow query will keep a connection occupied longer. The diagnosis changes the fix. Before changing limits, I would want to know:
- How many connections are open, active, and idle?
- How long do requests wait to acquire a connection?
- Which queries occupy connections longest?
- Are transactions held open while waiting for another dependency?
- Are there lock waits?
- Is the application creating more connections than expected during scale-out?
- Are slow queries returning too much data or missing an index?
- Are non-critical jobs competing with interactive user work?
Without those answers, “increase the pool” is only a guess.
The useful model: connections are a shared, bounded resource
The more reliable model is:
A request borrows a connection, performs a short query or transaction, and returns it quickly. That means the rest of the architecture should work to minimise connection occupancy:
- Keep transactions short.
- Avoid holding database transactions open across network calls.
- Avoid loading data that the screen does not need.
- Use pagination and filtering for large collections.
- Index the queries that matter.
- Move non-critical work off the interactive request path.
- Bound application concurrency where the database is the limiting resource.
- Use an external pooler when the deployment model produces many short-lived application connections, after checking whether transaction-level pooling breaks session features the application relies on, such as prepared statements or session settings.
Some waiting is healthy. A small, bounded queue is better than allowing unlimited work to overwhelm the database; what matters is that the waiting is visible, controlled and proportionate.
Do not hold a database connection across user time
One failure mode is easy to introduce in multi-step workflows: a service starts a database transaction, creates a pending record, then waits for a user to complete a payment flow or for an external provider to respond. That connection is now held across human and network time.
That model does not survive concurrency well, because user and provider delays become database resource consumption. A safer model persists the state and releases the connection:
This is where workflow state, idempotency and connection management meet: durable state lets a system wait without holding scarce resources open.
When a bigger pool is actually justified
None of this means a pool should never grow. A larger pool can be appropriate when measurement shows:
- The database has spare capacity.
- Query latency is low.
- Connection acquisition wait is the dominant delay.
- The workload is short-lived and efficiently indexed.
- Scale-out behaviour is understood.
- Other clients and workers have been included in the connection budget.
The point is that pool size should be an observed capacity decision, not a proxy for user count. A connection budget might include:
The number that matters is the total possible connection pressure, not the value in one application configuration file.
The closing rule
Ten thousand concurrent users does not mean ten thousand database connections. Instead of asking what the connection limit should be, ask how much concurrent database work this workload can safely run, how long that work occupies a connection and where excess work should wait.
A connection is a scarce, shared resource rather than a user session. Borrow it briefly, use it efficiently, return it quickly, and measure where requests wait before increasing any limit.