When configuring a database connection in a backend application, you've probably seen code like this:
sqlDB.SetMaxOpenConns(5)
sqlDB.SetMaxIdleConns(1)
sqlDB.SetConnMaxIdleTime(2 * time.Minute)
sqlDB.SetConnMaxLifetime(30 * time.Minute)
Most developers understand that these settings are related to database connections.
But the bigger questions are:
- What does
MaxOpenConnsactually control? - What is the difference between
MaxIdleConnsandConnMaxIdleTime? - Does having 50 concurrent users mean you need 50 MySQL connections?
- How many database connections should your application actually have?
- What happens when your connection pool becomes full?
The short answer to one of the most common questions is:
50 concurrent users do not mean you need 50 MySQL connections.
Understanding this distinction is important when building scalable backend applications—especially for SaaS and multi-tenant systems.
Let's break it down.
What Is a MySQL Database Connection?
A database connection is simply a communication channel between your application and the MySQL server.
For example, when a user opens their dashboard:
User
↓
Frontend
↓
Backend API
↓
MySQL Connection
↓
MySQL Database
Your backend might execute a query like:
SELECT * FROM users WHERE id = 10;
The backend sends the query through a database connection, MySQL processes it, and the result is returned to the application.
A simple request might look like this:
Request arrives
↓
Backend needs data
↓
Gets a database connection
↓
Executes query
↓
Receives result
↓
Returns connection to the pool
That last step—returning the connection to the pool—is where connection pooling becomes important.
Why Not Create a New MySQL Connection for Every Request?
Technically, you could create a new database connection for every request:
Request 1 → Create Connection → Run Query → Close
Request 2 → Create Connection → Run Query → Close
Request 3 → Create Connection → Run Query → Close
But this is inefficient.
Creating a database connection can involve:
- Network communication
- Authentication
- Server-side resource allocation
- Session initialization
- Additional CPU and memory usage
If your application receives hundreds or thousands of requests, constantly creating and destroying connections creates unnecessary overhead.
Instead, backend applications use a database connection pool.
What Is a Database Connection Pool?
A connection pool is a collection of reusable database connections.
Instead of creating a new connection for every request, your application can borrow an available connection from the pool.
Backend Application
│
▼
Connection Pool
┌────────┬────────┬────────┐
│ C1 │ C2 │ C3 │
└────────┴────────┴────────┘
│
▼
MySQL
The lifecycle looks like this:
Request
↓
Borrow Connection
↓
Execute Query
↓
Query Finished
↓
Return Connection to Pool
Then another request can reuse the same connection:
Request A → Uses C1 → Query Finished → C1 returns to pool
Request B → Uses C1
This is much more efficient than constantly creating and closing database connections.
Understanding MaxOpenConns in Go
Let's start with the most important setting:
sqlDB.SetMaxOpenConns(5)
This means:
Your application can have a maximum of 5 open database connections at the same time.
However, there is an important detail:
MaxOpenConns = 5does not mean Go immediately creates 5 connections when your application starts.
Connections are generally created as needed.
For example:
Application starts
↓
No database activity
↓
0 connections may exist
Then traffic arrives:
Request 1 → Connection C1 created
Request 2 → Connection C2 created
Request 3 → Connection C3 created
The pool can continue opening connections until it reaches:
Maximum Open Connections = 5
Once all 5 connections are busy, new requests that need a database connection may have to wait.
Do 50 Concurrent Users Need 50 MySQL Connections?
No.
This is one of the most common misunderstandings when designing backend infrastructure.
The key concept is:
Concurrent users are not the same as concurrent database queries.
Imagine your application has:
50 users online
Those users may be doing completely different things:
- Reading a page
- Looking at previously loaded data
- Typing into a form
- Waiting before clicking something
- Calling an API that doesn't require the database
- Running a database query
At a specific moment, perhaps only 5 requests actually need MySQL.
Your system might look like this:
50 Concurrent Users
│
▼
Backend Application
│
▼
Connection Pool
│
├── C1 → Running Query
├── C2 → Running Query
├── C3 → Running Query
├── C4 → Running Query
└── C5 → Running Query
The remaining users do not automatically need their own database connection.
Practical Example: 50 Users With Only 5 Database Connections
Let's say you have:
50 concurrent users
5 database connections
Each database query takes approximately:
50 milliseconds
At the first moment:
0ms
C1 → Request 1
C2 → Request 2
C3 → Request 3
C4 → Request 4
C5 → Request 5
After approximately 50 milliseconds, those queries finish.
Now the same connections can serve new requests:
50ms later
C1 → Request 6
C2 → Request 7
C3 → Request 8
C4 → Request 9
C5 → Request 10
The connections are continuously reused.
This is why:
A small connection pool can support many users when queries are fast and connections are released quickly.
The important factor isn't simply how many users exist.
It's how many requests need database access at the same time and how long those database operations take.
What Happens When All Database Connections Are Busy?
Now let's change the scenario.
You still have:
MaxOpenConns = 5
But instead of taking 50 milliseconds, each query takes:
2 seconds
Now the situation looks like this:
5 Connections
↓
5 Long-Running Queries
↓
All Connections Busy
↓
New Database Requests Wait
If many requests arrive simultaneously:
50 Requests
│
├── 5 → Running queries
│
└── 45 → Waiting for a connection
This is called connection pool contention.
Your application isn't necessarily slow because it has too few users or too little CPU.
The problem may simply be that database connections are occupied for too long.
Connection Pool Size Depends on Workload, Not User Count
This is the most important takeaway:
Don't calculate database connections based only on the number of concurrent users.
Instead, consider:
- How many requests need database access?
- How many database queries run simultaneously?
- How long does each query take?
- How powerful is the database server?
- How much CPU and memory are available?
- How many application instances are running?
For example:
100 Users
×
Fast Queries
=
Maybe a Small Pool Is Enough
But:
20 Users
×
Slow Queries
=
You May Still Have Connection Contention
The workload matters more than the user count.
What Does MaxIdleConns Mean?
Now let's look at:
sqlDB.SetMaxIdleConns(1)
This controls how many unused connections the pool can keep available.
In simple terms:
Keep up to 1 idle connection ready for future requests.
Imagine your application temporarily opens 3 connections:
C1 → Running Query
C2 → Running Query
C3 → Running Query
Eventually, all queries finish:
C1 → Idle
C2 → Idle
C3 → Idle
But you configured:
sqlDB.SetMaxIdleConns(1)
Conceptually, the pool will only retain up to one idle connection for reuse, while excess idle connections can be closed.
C1 → Keep Available
C2 → May Be Closed
C3 → May Be Closed
This can be useful for low-traffic applications where keeping many unused database connections open provides little benefit.
ConnMaxIdleTime: How Long Can a Connection Stay Unused?
Consider:
sqlDB.SetConnMaxIdleTime(2 * time.Minute)
This means:
A connection that remains idle for approximately 2 minutes becomes eligible to be closed.
Example:
10:00 → Connection created
10:01 → Query executed
10:01 → Connection becomes idle
10:02 → Still idle
10:03 → Idle for approximately 2 minutes
↓
Eligible to be closed
The important detail is:
The idle timer is based on when the connection was last used—not when it was created.
So if a connection is frequently reused, it won't become idle for long enough to be removed.
ConnMaxLifetime: How Long Can a Connection Live?
Now consider:
sqlDB.SetConnMaxLifetime(30 * time.Minute)
This controls the total age of a connection.
In simple terms:
A connection should not live forever. After approximately 30 minutes, it becomes eligible for replacement.
Example:
10:00 → Connection created
10:05 → Query
10:10 → Query
10:15 → Query
10:20 → Query
10:25 → Query
10:30 → Connection is approximately 30 minutes old
↓
Eligible for replacement
Even if the connection has been actively used, its total lifetime has reached the configured limit.
Connection lifetime limits can be useful in environments involving:
- Database failovers
- Network infrastructure changes
- Load balancers
- Long-running applications
- Server-side connection policies
ConnMaxIdleTime vs ConnMaxLifetime
These two settings are easy to confuse.
Here's the simplest way to remember them.
ConnMaxIdleTime
Ask:
How long can a connection sit unused?
Last Query
↓
Becomes Idle
↓
Idle Too Long
↓
Eligible to Close
ConnMaxLifetime
Ask:
How old can the connection become?
Connection Created
↓
Time Passes
↓
Maximum Lifetime Reached
↓
Eligible for Replacement
So:
ConnMaxIdleTime
→ Limits unused time
ConnMaxLifetime
→ Limits total connection age
The Restaurant Analogy: Understanding Connection Pools Easily
A simple way to understand connection pooling is to think about a restaurant.
Imagine the restaurant has:
50 customers
Does it need:
50 tables?
Not necessarily.
If customers finish eating and leave, the same tables can serve new customers.
Customer 1 → Uses Table 1 → Leaves
Customer 2 → Uses Table 1
Database connections work similarly:
Request 1 → Uses Connection C1 → Finishes
Request 2 → Uses Connection C1
Now imagine every customer stays at their table for two hours.
With only 5 tables:
5 Tables
↓
All Occupied
↓
Everyone Else Waits
This is similar to slow database queries.
When queries take too long, connections remain occupied, and new requests must wait.
How Much Memory Does a MySQL Connection Use?
Another common question is:
How much RAM does one MySQL connection consume?
There isn't a single fixed answer.
A MySQL connection does not always consume exactly:
1 MB
2 MB
5 MB
Memory usage depends on several factors, including:
- MySQL configuration
- Session settings
- Query complexity
- Sorting operations
- JOIN operations
- Temporary tables
- Per-session buffers
- Prepared statements
- Concurrent workload
So this calculation is often misleading:
5 Connections × 2 MB = 10 MB
MySQL memory usage is more complex.
A simplified view looks like this:
Database Server Memory
│
├── MySQL Global Memory
├── InnoDB Buffer Pool
├── Connection / Session Memory
├── Query Buffers
├── Temporary Memory
└── Operating System Overhead
This is why allowing thousands of connections on a small database instance can create serious resource pressure.
More connections do not automatically mean better performance.
Connection Pooling in Multi-Tenant SaaS Applications
Connection pooling becomes especially important in multi-tenant SaaS architecture.
Imagine you have:
200 tenants
And every tenant can create up to:
5 database connections
The theoretical maximum becomes:
200 × 5 = 1000 connections
This does not mean all 1,000 connections will always be open.
But if many tenants become active simultaneously, the total number of connections can increase significantly.
For example:
Tenant A → 5 connections
Tenant B → 5 connections
Tenant C → 4 connections
Tenant D → 5 connections
Tenant E → 3 connections
...
Across hundreds of tenants, a seemingly small pool size can become a large system-wide database load.
So the question shouldn't only be:
Is 5 connections enough for one tenant?
You should also ask:
What happens when 200 tenants become active at the same time?
This is where database capacity planning becomes important.
A Practical Connection Pool Configuration for Low-Traffic Applications
Suppose you have a relatively low-traffic application or tenant.
You might start with:
sqlDB.SetMaxOpenConns(5)
sqlDB.SetMaxIdleConns(1)
sqlDB.SetConnMaxIdleTime(2 * time.Minute)
sqlDB.SetConnMaxLifetime(30 * time.Minute)
Conceptually:
MaxOpenConns = 5
→ Maximum number of open connections
MaxIdleConns = 1
→ Keep up to 1 unused connection ready
ConnMaxIdleTime = 2 minutes
→ Remove connections that stay unused for too long
ConnMaxLifetime = 30 minutes
→ Periodically recycle older connections
However, these values are not universal magic numbers.
They may be appropriate for one application and completely wrong for another.
The correct configuration depends on real production metrics.
How to Tune Your Database Connection Pool
One of the biggest mistakes is assuming:
More database connections = better performance
That's not always true.
For example:
MaxOpenConns = 5
could perform better than:
MaxOpenConns = 100
if your database cannot efficiently process 100 concurrent queries.
Instead of guessing, monitor your system.
Important metrics include:
- Active database connections
- Open and idle connections
- Connection wait count
- Connection wait duration
- Query latency
- Slow queries
- Database CPU usage
- Database memory usage
- Application response time
In Go, you can inspect your connection pool using:
stats := sqlDB.Stats()
fmt.Println("Open Connections:", stats.OpenConnections)
fmt.Println("In Use:", stats.InUse)
fmt.Println("Idle:", stats.Idle)
fmt.Println("Wait Count:", stats.WaitCount)
fmt.Println("Wait Duration:", stats.WaitDuration)
If WaitCount continues increasing, it may indicate that requests are frequently waiting for an available connection.
At that point, you might consider increasing MaxOpenConns.
But don't increase it blindly.
When Increasing MaxOpenConns Makes Things Worse
Imagine your database already has:
High CPU Usage
High Memory Usage
Slow Queries
Increasing your connection pool from:
5 → 50
may allow more queries to hit the database simultaneously.
That can make the situation even worse.
Sometimes the real problem looks like this:
Slow Query
↓
Missing Index
↓
Query Takes Longer
↓
Connection Stays Busy
↓
Requests Start Waiting
In that situation, the best solution might be:
Optimize the query and add proper indexes.
Not:
Keep increasing the connection pool size.
A connection pool can manage database access efficiently, but it cannot fix poorly performing queries.
A Simple Mental Model for Database Connections
Think about your system like this:
Users
↓
HTTP Requests
↓
Backend Application
↓
Connection Pool
↓
MySQL Database
The number of:
Users
does not directly determine the number of:
Database Connections
What matters more is:
Concurrent Database Work
×
How Long Each Query Holds a Connection
Fast queries release connections quickly.
Slow queries keep connections busy.
That's the fundamental relationship.
Final Thoughts: How Many MySQL Connections Do You Really Need?
A database connection pool is essentially a resource management system.
You don't need one database connection for every user.
Instead, a relatively small number of connections can be shared across many users and requests.
The four key settings are:
MaxOpenConns
→ Maximum number of open database connections
MaxIdleConns
→ Maximum number of unused connections kept ready
ConnMaxIdleTime
→ How long an unused connection can remain idle
ConnMaxLifetime
→ How old a connection can become before being recycled
The most important lesson is:
Never determine your database connection pool size simply by looking at the number of concurrent users.
Instead, consider:
- Concurrent database workload
- Query execution time
- Connection wait time
- Database CPU capacity
- Available memory
- Application architecture
- Number of application instances
- Total system-wide database connections
This becomes especially important in multi-tenant SaaS systems, where a small connection pool per tenant can eventually create hundreds or thousands of connections across the entire infrastructure.
Redis helps by reducing how often your application needs to go to MySQL at all.
The mechanism: Cache-Aside Pattern
Without Redis:
User Request
↓
Backend
↓
MySQL Connection Pool
↓
MySQL Query
↓
Return Data
Every request that needs the same data may hit MySQL.
With Redis:
User Request
↓
Backend
↓
Check Redis
│
├── Cache HIT → Return data immediately
│
└── Cache MISS → Query MySQL
↓
Save in Redis
↓
Return data
Practical Example
Imagine 50 users open the same dashboard.
The dashboard needs this data:
SELECT * FROM company_settings WHERE tenant_id = 101;
Without Redis
Potentially:
50 Users
↓
50 Requests
↓
MySQL
↓
Same query executed many times
Your MySQL connection pool might become busy:
5 Connections
↓
5 Queries Running
↓
Other Requests Wait
With Redis
The first request:
User 1
↓
Redis: Data not found ❌
↓
MySQL Query
↓
Get Data
↓
Save Data in Redis
Now Redis contains:
Key:
tenant:101:settings
Value:
{
"theme": "dark",
"company_name": "ABC Ltd"
}
The next 49 users:
User 2 → Redis HIT ✅
User 3 → Redis HIT ✅
User 4 → Redis HIT ✅
...
User 50 → Redis HIT ✅
Now the flow becomes:
50 Users
↓
Backend
↓
Redis
↓
Most requests never reach MySQL
How Redis Helps Your Connection Pool
Redis does not directly increase your MySQL connection limit.
Instead, it reduces the pressure on MySQL.
Without Redis:
50 Requests
↓
50 Requests need MySQL
↓
Only 5 connections available
↓
Requests wait
With Redis:
50 Requests
↓
45 Cache HITs → Redis
5 Cache MISSes → MySQL
Now only a small number of requests need MySQL connections.
So your existing configuration:
sqlDB.SetMaxOpenConns(5)
can potentially handle much more traffic because many requests no longer need a database connection.
The Simple Relationship
Think of it like this:
Without Redis:
Users
↓
Backend
↓
MySQL Connection Pool
↓
MySQL
With Redis:
Users
↓
Backend
↓
Redis Cache
│
├── HIT → Return Data ⚡
│
└── MISS → MySQL Connection Pool
↓
MySQL
So the key idea is:
Connection pooling manages how your application accesses MySQL. Redis reduces how often your application needs to access MySQL.
They solve different problems, but together they can significantly improve scalability.
One-line summary:
MySQL Connection Pool = manages database connections efficiently. Redis = prevents unnecessary database queries.
For your multi-tenant SaaS example, Redis can be especially useful for frequently requested but rarely changing data such as tenant settings, user permissions, dashboard summaries, configuration data, and frequently accessed lookup data.
The goal is not to create as many database connections as possible.
The goal is simple:
Use enough connections to efficiently handle your workload—without overwhelming your database.
Key Takeaway
If you remember only one thing from this article, remember this:
Concurrent users ≠ concurrent database connections.
A well-configured connection pool allows a small number of database connections to efficiently serve a much larger number of users—provided your queries are fast and your database is properly monitored.
Top comments (0)