Optimize database connection management with connection pooling strategies. Covers pool sizing, PgBouncer configuration, connection lifecycle, health checks, pool monitoring, and the patterns that prevent connection exhaustion under load.
Database connection pooling is a technique for reusing and managing a set of database connections to minimize the overhead of establishing new connections and to improve response time under load. It matters when applications issue frequent, short-lived queries — such as web services under concurrent user traffic — where the cost of opening a TCP connection, authenticating, and initializing a session outweighs the cost of the actual query.
Applications that execute many queries per second with short-lived transactions benefit most from connection pooling. A typical web service handling 1000 requests per second with an average query latency of 50ms will spend more time setting up database connections than executing queries. Without pooling, each request might open a new connection, leading to connection exhaustion, high latency, and resource contention.
The key signal that connection pooling is required is a connection timeout or too many open connections error from the database. Common error texts include:
FATAL: too many connections for role "app_user"
ERROR: could not connect to server: Connection timed out
ERROR: could not connect to server: Connection refused
These errors indicate that the database server is unable to accept new connections, often due to a bottleneck at the TCP layer or a limit on max_connections in PostgreSQL, thread_pool_size in MySQL, or max_connections in SQL Server.
A connection pool maintains a set of pre-established database connections. When a client requests a connection:
Connections can be in one of three states: *idle*, *in-use*, or *closed*. The pool manages the lifecycle of connections, including:
The core configuration parameters for a connection pool are:
min_connections – the number of connections kept open at all timesmax_connections – the maximum number of connections the pool can holdconnection_timeout – how long to wait when acquiring a connectionidle_timeout – how long a connection can be idle before being closedmax_lifetime – how long a connection can live before being replacedvalidation_query – an SQL query to test if a connection is still validFor example, in a PostgreSQL-backed web application using a connection pooler like PgBouncer, the configuration in pgbouncer.ini includes:
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
server_reset_query = DISCARD ALL
server_check_query = SELECT 1
server_check_interval = 30
server_check_timeout = 3
The server_check_query runs periodically to validate all active connections. If it fails, the pool marks the connection as invalid and removes it from the pool.
Applications acquire connections through a standard API. In a Go service using database/sql, the code looks like:
db, err := sql.Open("postgres", "host=postgres user=app password=secret dbname=app")
if err != nil {
log.Fatal(err)
}
// Set pool configuration
db.SetMaxOpenConns(100)
db.SetMaxIdleConns(20)
db.SetConnMaxLifetime(10 * time.Minute)
db.SetConnMaxIdleTime(5 * time.Minute)
// Use the connection
rows, err := db.Query("SELECT id, name FROM users WHERE active = $1", true)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
The SetMaxOpenConns configures the maximum number of connections the pool allows. When a new connection is needed and the pool is full, the pool blocks until a connection becomes available.
The Query call returns a *sql.Rows, which holds a reference to a connection from the pool. The connection remains in use until Close is called on the rows or the application finishes processing the result set.
A connection leak occurs when connections are acquired but never returned to the pool. This leads to a gradual increase in the number of open connections and eventual connection exhaustion.
Common causes:
rows.Close()tx, err := db.Begin() when using transactionsdb.QueryContext with a context that never expires)Example of a leak:
func GetUsers() []User {
rows, err := db.Query("SELECT id, name FROM users")
if err != nil {
log.Fatal(err)
}
// Missing: defer rows.Close()
var users []User
for rows.Next() {
var u User
rows.Scan(&u.ID, &u.Name)
users = append(users, u)
}
return users // rows still open, connection not returned
}
Connections that sit idle for longer than idle_timeout are closed and removed from the pool. If the pool is configured with idle_timeout = 300, connections become invalid after 5 minutes of inactivity.
This is especially noticeable when:
tcp_idle_timeoutThe pool must validate connections before using them. A common validation query is SELECT 1, which returns a single row with value 1.
Connections have a maximum lifetime (max_lifetime), after which they are retired and replaced. This prevents issues like stale connections due to:
When a connection reaches its max_lifetime, the pool:
This rotation ensures that the pool remains healthy and responsive even under long-running workloads.
If a connection fails during validation, the pool marks it as invalid and retries. The validation_query runs periodically, typically every server_check_interval seconds.
If the query fails (e.g., SELECT 1 returns no result or raises an error), the pool:
This is critical for detecting and recovering from transient network issues.
Applications should monitor the pool's state. Key metrics include:
In a service using pgBouncer, the SHOW CLIENTS command returns a list of all active client connections:
SHOW CLIENTS;
Output:
client_addr | client_port | user | db | state | conn_time | query | total_time | query_time
---------------+-------------+--------+-------+---------+-------------+----------+---------------+---------------
10.0.0.10 | 54321| app | app | active | 2023-10-05 | SELECT 1 | 1.2s | 0.8s
10.0.0.11 | 54322| app | app | idle | 2023-10-05 | | 15.3s | 0.0s
This output shows that one client is actively executing a query, while another is idle.
The SHOW STATS command provides aggregate statistics:
SHOW STATS;
Output:
total_conns | active_conns | idle_conns | waiting_conns | total_time | total_query_time
---------------+---------------+-------------+----------------+-------------+-------------------
247 | 102 | 145 | 10 | 1257.4s | 893.2s
This reveals that the pool has 247 total connections, with 102 active, 145 idle, and 10 waiting. The total time spent serving queries is 1257 seconds, with 893 seconds spent on actual query execution.
min_connections to the expected number of concurrent users.max_connections to the database’s max_connections value (typically 100–1000).max_lifetime to align with database-side timeouts (e.g., tcp_idle_timeout, idle_in_transaction_session_timeout).validation_query to validate connections; SELECT 1 is sufficient for most cases.connection_timeout to 10–30 seconds for web services.SetConnMaxLifetime and SetConnMaxIdleTime to ensure connections are refreshed regularly.SHOW CLIENTS and SHOW STATS in production to detect drift and failures.max_connections vs max_open_conns: max_connections is a database server setting; max_open_conns is a pool-level setting. A pool may open 200 connections, but the database may only allow 100. The pool must manage this boundary.idle_timeout vs max_lifetime: idle_timeout applies to idle connections only; max_lifetime applies to all connections, regardless of activity.validation_query runs on each connection acquisition and periodically. If the query is slow or complex, it adds overhead.Connection pooling is a foundational technique for high-performance database access. It reduces latency, increases throughput, and prevents connection exhaustion. The key is to configure the pool to match the application’s workload and the database’s capacity. By monitoring and tuning min_connections, max_connections, idle_timeout, max_lifetime, and validation_query, engineers ensure that the database layer scales efficiently with the application.
This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy