Go database sql Connection Pool Tuning for Gin APIs
Quick answer
Go's database/sql package manages a connection pool for you, and its default settings are rarely right for a production API. Set a maximum number of open...
Go's database/sql package manages a connection pool for you, and its default settings are rarely right for a production API. Set a maximum number of open connections sized against your database capacity, keep a sensible number idle, and limit connection lifetime. The pool size should be small enough that all application instances together never exceed what the database can handle.
What Do the Pool Settings Control?
The sql.DB value is not a single connection. It is a pool that opens connections on demand and reuses them. Four settings shape its behavior, and each addresses a different problem.
- SetMaxOpenConns: the maximum number of connections in use or idle. The default is unlimited, which can overwhelm a database under load.
- SetMaxIdleConns: how many unused connections are kept ready for reuse. The default is small, which can cause constant reconnecting under bursty traffic.
- SetConnMaxLifetime: the maximum age of a connection before it is closed and replaced.
- SetConnMaxIdleTime: how long an idle connection may sit before being closed.
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(30 * time.Minute)
db.SetConnMaxIdleTime(5 * time.Minute)
These values are examples, not recommendations. The right numbers depend on your database, workload, and number of application instances.
How Do You Size the Pool?
Start from the database, not from the application. A database can usefully run only so many queries at once, and more connections beyond that point add contention without adding throughput. Then divide the budget among every instance that connects.
If the database should handle about 100 connections in total and you run four API instances, each instance might get roughly 20 to 25 at most, leaving headroom for migrations, admin tools, and background workers. Autoscaling changes the math, because the number of instances grows exactly when load rises. Set the per-instance limit with the maximum instance count in mind, or put a server-side pooler in front.
Smaller pools are often faster than large ones. A modest pool forces excess requests to wait briefly in the application, which is cheaper than overloading the database. Measure throughput and latency while changing the size.
Why Do Lifetimes and Idle Settings Matter?
Long-lived connections can be silently dropped by load balancers, firewalls, or database restarts. Setting a maximum lifetime shorter than any infrastructure timeout between your app and the database avoids using connections that have already been cut. It also lets the pool rebalance after failovers and DNS changes.
Idle limits trade readiness against resources. Keeping idle connections open means bursts are served without connection setup latency. Closing them after a while releases database resources during quiet periods. If MaxIdleConns is lower than MaxOpenConns, connections above the idle limit are closed after use and reopened later, which can cause churn under bursty traffic.
- Keep MaxIdleConns equal to or close to MaxOpenConns for steady API traffic.
- Set the lifetime below network and database idle timeouts.
- Add jitter if many instances start together and would otherwise recycle connections simultaneously.
How Do You Monitor the Pool?
The db.Stats method returns the pool's current state, including open connections, in-use and idle counts, the number of times requests had to wait, and the total wait duration. Export these as metrics and alert on growing wait counts.
Rising wait time means requests are queueing for connections. Before raising the limit, check whether queries are slow, transactions are held too long, or connections are leaking. A leak usually comes from forgetting to close rows or from transactions that are never committed or rolled back.
- Always close result sets, and check for errors after iterating.
- Use context timeouts so a stuck query cannot hold a connection forever.
- Keep transactions short and avoid network calls while holding one.
- Confirm driver-specific behavior in the documentation of the driver you use.
Frequently Asked Questions
What is the default max open connections in Go?
Unlimited. Without a limit, a traffic spike can open more connections than the database allows, so set an explicit value.
Should max idle equal max open?
For steady API traffic, often yes or close to it, which avoids reconnect churn. Adjust if you need to release resources during quiet periods.
How do I know if the pool is too small?
Check the wait count and wait duration in db.Stats. Persistent waiting suggests contention, though slow queries may be the real cause.
Do I need an external pooler too?
If many instances connect, a server-side pooler can cap total connections. Check its compatibility with your database features.
Summary
Set explicit pool limits based on database capacity and instance count, tune lifetimes below infrastructure timeouts, and monitor wait metrics. Fix leaks and slow queries before enlarging the pool. Consult the database/sql and driver documentation for version details.