Adding more database connections often degrades performance instead of improving it because each connection consumes memory, CPU, and locks, and beyond a certain point the database spends more time managing connections than executing queries. The exact threshold varies by system, but the principle holds across PostgreSQL, MySQL, and most other relational databases: more concurrent connections means more contention, not more throughput.
Formula
This simple model shows the tradeoff: throughput = connections / total time. If you have 10 connections and 60 seconds, you get 0.17 queries per second per connection, or 10 queries per second total. Doubling connections to 20 in the same 60 seconds gives 20 queries per second only if the database can actually run them in parallel. In reality, the database has finite CPU cores, disk I/O, and memory bandwidth, so the real throughput curve looks like a hill: it rises with connections, then flattens and falls.
Background: How Databases Handle Connections
Every open connection is not just a network socket. The database server allocates a backend process or thread, a memory buffer for that session, and often a transaction context. In PostgreSQL, each connection spawns a separate OS process with its own cache; in MySQL, each connection gets a thread and a set of session variables. With 100 connections, the server might be using 10% of its RAM just for connection overhead. More importantly, the database’s lock manager and buffer pool are shared, so every additional connection competes for the same resources.
How Connection Pooling Works
Connection pooling is the standard solution. A pool holds a fixed number of connections (say 20) that are reused by many application threads. Instead of opening a new connection per request, the app borrows one from the pool, runs the query, and returns it. This keeps the database connection count low and stable. Most web frameworks and ORMs (like Hibernate, Django, or Rails) include built-in pooling. The pool size should be tuned: a common formula is pool size = (number of CPU cores × 2) + number of disks, but that is a starting point, not a rule. For SSD-backed databases, a smaller pool often works better because disk I/O is fast and CPU becomes the bottleneck.
Common Problems: What Actually Goes Wrong
- Context switching: When the OS has more runnable threads than CPU cores, it constantly switches between them. Each switch wastes CPU cycles, and with hundreds of connections, the database can spend more time switching than executing.
- Lock contention: Row-level locks and table locks become a bottleneck. If 50 connections all try to update the same row, 49 of them wait. The waiters hold their own connections open, consuming resources while doing nothing.
- Deadlocks: More concurrent transactions increase the chance of deadlocks, where two connections wait on each other’s locks. The database must detect and kill one, causing a retry that adds load.
- Memory pressure: Each connection reserves memory for sorting, temporary tables, and result sets. With too many connections, the buffer pool (the cache that holds hot data) shrinks, causing more disk reads.
- Connection storm: When an app restarts and tries to open 500 connections at once, the database may become unresponsive during the handshake process, making the problem worse.
Comparisons: PostgreSQL vs MySQL vs Others
PostgreSQL uses one OS process per connection, so 100 connections means 100 processes. That is heavy but isolated: a crash in one does not take down the server. MySQL uses threads, which are lighter per connection, but all threads share the same process, so a memory leak in one can affect all. Both hit the same wall: the optimal connection count is roughly equal to the number of CPU cores, plus a few for I/O wait. For example, a 4-core server typically performs best with 8–16 connections. Going beyond that to 100 or 200 rarely adds throughput—it usually reduces it. Newer PostgreSQL versions (14+) have improved connection handling with scalable locks, but the fundamental limit remains.
How to Choose the Right Connection Count
You cannot know the optimal number without testing, but you can start with a sane baseline and measure. Here is a practical approach:
- Start with a pool size equal to the number of CPU cores on your database server.
- Run a load test with that pool size and measure queries per second and latency.
- Increase the pool by 25% and repeat. Keep increasing until throughput stops improving or latency spikes.
- Watch the database’s CPU and wait events. If CPU is high but disk I/O is low, you are CPU-bound—reduce the pool. If you see long lock waits, reduce the pool.
- Set a hard limit on the database’s max connections (e.g.,
max_connections = 100in PostgreSQL) to prevent runaway apps from exhausting memory.
A common mistake is setting the pool size to the same as the number of concurrent users. If you have 1,000 users, you do not need 1,000 connections—most users are idle between requests. A pool of 20–50 can easily serve 1,000 users if queries are fast.
Safety and Monitoring
Before changing connection limits, check your database’s current usage. In PostgreSQL, query pg_stat_activity to see active connections. In MySQL, use SHOW PROCESSLIST. Look for connections that are idle in transaction—those hold locks and should be closed. Set alerts for connection count and lock wait time. If you see frequent too many connections errors, your pool is too large or your database is undersized. Always test changes in a staging environment first, because a wrong setting can make the database unavailable.
Alternatives: When Pooling Is Not Enough
If you have tuned the pool and still hit performance walls, consider these options:
- Read replicas: Move read-only queries to a replica to reduce load on the primary.
- Query optimization: Slow queries hold connections longer. Index missing columns, reduce the number of round trips, and avoid
SELECT *. - Cache results: Use Redis or Memcached to serve hot data without hitting the database.
- Queue writes: Instead of writing directly, push to a message queue and let a worker batch writes.
- Sharding: Split data across multiple databases, but only after you have exhausted simpler options.
Frequently Asked Questions
Why does my app slow down when I increase the connection pool from 10 to 100?
Because the database now spends time managing 90 extra connections. Each connection consumes memory and CPU for context switching, and they all compete for the same locks and buffer cache. The result is higher latency per query, even if total throughput stays the same.
What is the ideal number of connections for a 4-core database?
A common starting point is 8–12 connections. You should load-test to find the exact number, but going above 20 on a 4-core server rarely helps. The optimal number is roughly the number of CPU cores plus a small margin for I/O waits.
Does connection pooling always help?
Yes, if the pool size is set correctly. A pool prevents the overhead of opening and closing connections for every request, which is expensive. But a pool that is too large reintroduces the same contention problems.
What is the difference between max_connections and pool size?max_connections is the absolute limit the database will accept; if you exceed it, new connections are rejected. Pool size is how many connections your application keeps open at once. The pool size should be well below max_connections so there is headroom for administrative tasks.
Can I use a connection pool with a serverless database?
Serverless databases often have their own connection management and may charge per connection. In that case, a small pool (1–5) is usually best, because the database scales automatically and idle connections still cost money.
Related
Formula used
in t | Total time | seconds | 60 | 1 | 86400 in c | Connections | count | 10 | 1 | 1000 out per_conn | Time per connection | seconds | t / c | 2 out throughput | Queries per second | qps | c / t | 2
Each line is one input (in, sel, list) or one result (out: label, unit, formula, decimals). How the calculators work
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.
More cards in this drawer
- Poco C51 Airtel Prepaid Locked Variant India?
- Jenkins vs GitHub Actions: Which CI/CD Fits Your Team?
- Turn Off Backup Encryption in iTunes?
- Campus Tech Innovation Hubs: What They Are
- Shared VPS Hosting: Is It Better?
- Disk Defrag Pro: What It Does and Whether You Need It