go-redis/redis · error

redis: connection pool timeout

Error message

redis: connection pool timeout

What it means

pool.ErrPoolTimeout is returned when waiting for a free connection exceeds Options.PoolTimeout (pool.go:64-65). It is produced by the semaphore acquire in waitForTurn (pool.go:1179) and the get path (pool.go:876/1238/1253). It means the pool is saturated AND the configured wait budget elapsed.

Source

Thrown at internal/pool/pool.go:65

	// MetricStateIdle indicates the connection is idle in the pool,
	// ready to be acquired.
	MetricStateIdle = "idle"

	// MetricStateUsed indicates the connection is currently being used
	// by a client operation.
	MetricStateUsed = "used"
)

var (
	// ErrClosed performs any operation on the closed client will return this error.
	ErrClosed = errors.New("redis: client is closed")

	// ErrPoolExhausted is returned from a pool connection method
	// when the maximum number of database connections in the pool has been reached.
	ErrPoolExhausted = errors.New("redis: connection pool exhausted")

	// ErrPoolTimeout timed out waiting to get a connection from the connection pool.
	ErrPoolTimeout = errors.New("redis: connection pool timeout")

	// ErrConnUnusableTimeout is returned when a connection is not usable and we timed out trying to mark it as unusable.
	ErrConnUnusableTimeout = errors.New("redis: timed out trying to mark connection as unusable")

	// errHookRequestedRemoval is returned when a hook requests connection removal.
	errHookRequestedRemoval = errors.New("hook requested removal")

	// errConnNotPooled is returned when trying to return a non-pooled connection to the pool.
	errConnNotPooled = errors.New("connection not pooled")

	// errConnEvictedIdle is passed to OnRemove hooks when a pooled connection is evicted on
	// Put because the idle pool is already at MaxIdleConns.
	errConnEvictedIdle = errors.New("connection evicted: idle pool at capacity")

	// metricCallbackMu protects all global metric callback functions for thread-safe access.
	metricCallbackMu sync.RWMutex

	// Global metric callbacks for connection state changes

View on GitHub (pinned to 36d97525cd)

Solutions

  1. Increase Options.PoolSize to reduce queueing.
  2. Increase Options.PoolTimeout (or set ReadTimeout high enough) to tolerate transient stalls.
  3. Identify and speed up slow/blocking commands; avoid holding connections across blocking calls.
  4. Add MinIdleConns so the pool warms up and dials proactively.

Example fix

// before
opt := &redis.Options{Addr: addr, PoolTimeout: 1 * time.Second}
// after
opt := &redis.Options{
    Addr:         addr,
    PoolSize:     64,
    MinIdleConns: 16,
    PoolTimeout:  10 * time.Second,
}
Defensive patterns

Strategy: retry

Validate before calling

// Choose PoolTimeout >= expected worst-case command latency.
opts := &redis.Options{Addr: addr, PoolSize: 64, PoolTimeout: 10 * time.Second}

Try / catch

for i := 0; i < 3; i++ {
    err := client.Get(ctx, key).Err()
    if err == nil { break }
    if errors.Is(err, redis.ErrPoolTimeout) {
        time.Sleep(time.Duration(i+1) * 50 * time.Millisecond)
        continue
    }
    return err
}

Prevention

When it happens

Trigger: All pool connections busy for longer than PoolTimeout (default ReadTimeout or 10s), e.g. many slow commands or a downstream stall, so a goroutine times out waiting in the wantConn queue.

Common situations: Slow Lua scripts, BLPOP/MULTI-EXEC holding conns, network latency spikes, or PoolTimeout set too low relative to command latency.

Related errors


AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06). Data as JSON: /data/errors/1b5b7a2fcbdae5c0.json. Report an issue: GitHub.