DEV Community

Manoir Yantai
Manoir Yantai

Posted on

Optimizing API Performance with Connection Pooling

Stop treating database connections as disposable JSON objects. Every connection handshake wastes CPU cycles and introduced latency. Connection pooling reuses established TCP connections, reducing overhead and increasing throughput. This is non-negotiable for any performance-sensitive API.

The default approach in many frameworks opens a new connection per request. That means three-way handshakes, authentication, and TLS every single time. For high-traffic endpoints, this kills response times and collapses under load. Connection pooling maintains a set of persistent connections, borrowing and returning them on demand. The pool handles lifecycle management—idle connections are kept alive, and failed ones are replaced.

But pools aren't magic. Misconfigured pools cause more harm than no pool at all. Common mistakes: too few connections queue requests, too many exhaust database resources, and no timeout lets stale connections hang forever.

Here’s a production-ready pool setup in Go using database/sql—which already includes a built-in pool:

import (
    "database/sql"
    "time"
)

func initDB() *sql.DB {
    db, err := sql.Open("postgres", dsn) // zero connections until used
    if err != nil {
        log.Fatal(err)
    }

    // Pool configuration
    db.SetMaxOpenConns(25)             // max concurrent connections
    db.SetMaxIdleConns(5)              // keep at least 5 idle
    db.SetConnMaxLifetime(30 * time.Minute) // recycle connections
    db.SetConnMaxIdleTime(5 * time.Minute)  // close unused idle

    return db
}
Enter fullscreen mode Exit fullscreen mode

This snippet limits total connections, prevents idle bloat, and forces periodic refresh. Tune these values based on your database server limits and traffic patterns. Start conservative—adding connections is cheaper than recovering from resource exhaustion.

Now, apply this to your API handlers. Every request uses the same *sql.DB instance. The pool handles concurrency safely. No handshake overhead per request.

Beyond basic setup, monitor pool health: db.Stats() gives InUse, Idle, and WaitCount. Spikes in WaitCount mean you need more SetMaxOpenConns. High InUse with low Idle suggests transactions holding connections too long—check row iteration or slow queries.

Connection pooling isn’t limited to databases. Redis, gRPC, and HTTP clients all benefit. Standardize pool usage across your stack for consistent performance.

Final rule: never open connections in request handlers. Use pools at the service layer. Your response times will thank you, and your database servers will stay responsive under peak load. Start with the defaults, measure, adjust, and move on to real problems.

Diagram

Top comments (0)