Connection Pooling
Every command your application sends to Redis travels over a TCP (or Unix domain socket) connection to the server. Opening a brand new connection for every single command is slow, because a connection has to complete a TCP handshake — and possibly authentication and a TLS handshake — before it can carry even one command. Connection pooling is the practice of keeping a set of already-open connections ready to be reused: your application code borrows a connection from the pool, sends its commands, and returns the connection instead of tearing it down and reconnecting every time. This lesson explains how Redis manages connections on the server side, why pooling matters given Redis’s single-threaded design, and how to inspect and reason about live connections directly from redis-cli.
Overview: How Connections Work in Redis
Redis is a client-server system. Each client — a redis-cli session, a web server process, a background worker — opens a TCP connection (or a Unix socket connection on the same host) to the Redis server and keeps it open for as long as it wants to send commands. Redis accepts many connections concurrently using an event loop, but the actual execution of commands happens one at a time on Redis’s single main thread. That single-threaded command execution is what makes every individual Redis command atomic: while Redis is running your INCR or HSET, no other client’s command can interleave in the middle of it, even though hundreds of clients may be connected simultaneously.
Every open connection costs the server a small but real amount of resources: a file descriptor, a per-client input/output buffer, and a slot counted against the maxclients setting (10000 by default). None of that is expensive in isolation, but it adds up, and — more importantly for application performance — establishing a connection is much more expensive than using one. A fresh TCP connection needs a three-way handshake; if you use TLS, add a full TLS handshake on top; if the server requires authentication, add an AUTH round trip before the first real command can even be sent. Doing all of that for a single GET and then closing the connection can easily cost more time than the command itself.
Why pooling matters
This is why virtually every Redis client library (in Python, Java, Node.js, Go, and so on) ships with a connection pool: a fixed or elastic set of long-lived connections that application code checks out, uses, and returns, instead of opening and closing a connection per request. Because Redis processes one command at a time per connection, and each connection can only be \”in use\” by one caller at a time, the pool’s job is really about avoiding reconnect overhead and giving concurrent request handlers (threads, coroutines, workers) enough parallel connections that they aren’t all queued up waiting for one shared socket. The exact API for configuring a pool (minimum/maximum size, idle timeout, health checks) is client-library-specific and covered in this course’s client-library lessons — but the underlying Redis-side concepts, and the commands you use to observe connections, are the same no matter which language you’re pooling from, and that’s what this lesson focuses on.
Syntax
CLIENT ID
CLIENT GETNAME
CLIENT SETNAME connection-name
CLIENT INFO
CLIENT LIST [TYPE normal|master|replica|pubsub]
INFO [section]
CONFIG GET parameter
CLIENT ID— returns the unique integer ID Redis assigned to the current connection when it connected.CLIENT GETNAME/CLIENT SETNAME connection-name— get or set a human-readable name for the current connection, useful for telling pooled connections apart in diagnostics. Names cannot contain spaces.CLIENT INFO— returns a single line of detailed information about the current connection (address, name, age, idle time, last command, memory usage, and more).CLIENT LIST [TYPE ...]— likeCLIENT INFO, but one line per connection, for every connection on the server (optionally filtered by type).INFO [section]— returns server statistics as text; theclientssection reports how many clients are currently connected and the configured ceiling.CONFIG GET parameter— reads a server configuration value, such asmaxclients, the hard limit on simultaneous connections.
| Command | Time Complexity |
|---|---|
CLIENT ID |
O(1) |
CLIENT GETNAME / CLIENT SETNAME |
O(1) |
CLIENT INFO |
O(1) |
CLIENT LIST |
O(N), N = number of connected clients |
INFO |
O(1) |
CONFIG GET |
O(N), N = number of matching config parameters |
Examples
Example 1: Naming and identifying a connection
Client libraries often name their pooled connections internally so operators can tell them apart in CLIENT LIST output. Here’s what that looks like at the protocol level:
CLIENT ID
CLIENT SETNAME myapp-conn-1
CLIENT GETNAME
CLIENT INFO
Output:
(integer) 6
OK
\"myapp-conn-1\"
id=6 addr=127.0.0.1:52746 laddr=127.0.0.1:6379 fd=9 name=myapp-conn-1 age=0 idle=0 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 watch=0 qbuf=26 qbuf-free=20448 argv-mem=10 multi-mem=0 tot-net-in=100 tot-net-out=0 rbs=1024 rbp=0 obl=0 oll=0 omem=0 tot-mem=0 events=r cmd=client|info user=default redir=-1 resp=2 lib-name= lib-ver=
CLIENT ID gives this connection’s unique identifier (yours will differ). CLIENT SETNAME tags the connection, so a health check or an admin doing capacity planning can immediately see which application a connection belongs to. CLIENT INFO confirms the name stuck (name=myapp-conn-1) and shows the connection’s local/remote addresses, how long it’s been idle, and the last command it ran (cmd=client|info) — exactly the kind of detail you’d want when debugging a misbehaving connection pool in production.
Example 2: Seeing how many clients are connected
INFO clients
Output:
# Clients
connected_clients:1
cluster_connections:0
maxclients:10000
client_recent_max_input_buffer:0
client_recent_max_output_buffer:0
blocked_clients:0
tracking_clients:0
pubsub_clients:0
watching_clients:0
clients_in_timeout_table:0
total_watched_keys:0
total_blocking_keys:0
total_blocking_keys_on_nokey:0
connected_clients is the number that matters most when you’re sizing connection pools across multiple application instances: it’s a live count of every open connection on this Redis server right now, including pooled ones sitting idle. If you run this in a shell where only your redis-cli session is connected, you’ll see connected_clients:1. In production, watch this metric over time — a pool that keeps growing without bound, or an application that leaks connections instead of returning them to the pool, shows up here as a steadily climbing number.
Example 3: Checking the server’s connection ceiling
CONFIG GET maxclients
Output:
1) \"maxclients\"
2) \"10000\"
maxclients is the hard cap on simultaneous connections for this Redis server (10000 by default, minus a small number reserved for Redis’s own internal use). This is the number you must plan pool sizes against: if you run 50 instances of an application, each with a connection pool of 250, that’s up to 12,500 possible connections — already over the default ceiling before you count admin tools, replicas, or other services sharing the same Redis instance.
Example 4: Listing every connected client
CLIENT LIST TYPE normal
Output:
id=7 addr=127.0.0.1:52748 laddr=127.0.0.1:6379 fd=9 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 watch=0 qbuf=26 qbuf-free=20448 argv-mem=10 multi-mem=0 tot-net-in=26 tot-net-out=0 rbs=1024 rbp=0 obl=0 oll=0 omem=0 tot-mem=0 events=r cmd=client|list user=default redir=-1 resp=2 lib-name= lib-ver=
CLIENT LIST is CLIENT INFO for every connection at once — one line per client. TYPE normal filters out replica links and pub/sub connections so you’re only looking at ordinary command connections, which is usually what you want when auditing an application’s connection pool. In production this is the command to reach for when you suspect a pool is misbehaving: sort by age to find long-lived connections, by idle to find ones sitting unused, or grep by name= if your client library sets connection names.
How It Works Step by Step
When a pooled connection is created (usually the first time the pool needs more connections than it currently has), this is what happens:
- The client opens a TCP connection (or Unix socket) to the Redis server’s host and port.
- If the server is configured to require TLS, a TLS handshake happens next.
- If the server requires authentication, the client sends
AUTH(orHELLOwith credentials) before any other command will be accepted. - The client may send
SELECTto pick a logical database, orHELLO 3to switch to the RESP3 protocol. - The connection is handed to the pool as \”ready.\” From this point on, the pool can lend it out for any number of commands without repeating steps 1-4.
- Once checked out, each command the caller sends travels to Redis, is queued briefly if the single-threaded server is mid-command for another client, executes atomically, and returns its reply — all on this same TCP connection, in strict request/reply order.
- When the caller is done, the connection is returned to the pool idle rather than closed, ready for the next borrower.
- Idle pooled connections are eventually closed either by the client library (idle timeout) or by the server, if a
timeoutis configured server-side or the connection goes stale (for example, after a failover moves the primary to a different node).
The key detail is step 6: because a single connection processes commands strictly in the order they’re sent and returns replies in that same order, one connection can only safely be used by one logical caller at a time. That’s exactly the constraint a connection pool exists to manage.
Common Mistakes
Opening a new connection for every command instead of pooling. Code that does the equivalent of connect → SET → disconnect for every single write pays the full TCP/TLS/AUTH handshake cost on every operation, which can dwarf the cost of the command itself and severely limits throughput. The fix is always to hold connections open in a pool and reuse them across many commands and many requests.
Sizing pools without checking the server’s maxclients. If your fleet of application instances collectively opens more connections than Redis’s maxclients allows, new connection attempts start failing once the ceiling is hit:
PING
Output (when the server is already at its connection limit):
(error) ERR max number of clients reached
Check CONFIG GET maxclients and INFO clients (as shown in the examples above) and size pools so that (pool size) × (number of app instances) stays comfortably under the limit, with headroom for other tools and replicas.
Sharing a single pooled connection across concurrent threads without exclusive checkout. Because replies come back on a connection in the exact order commands were sent, two threads writing to the same connection at the same time can each read the other’s reply instead of their own. A pool must hand out a connection exclusively to one caller until it’s returned — never let two request handlers use the same live connection concurrently.
Best Practices
- Always reuse connections through a pool provided by your client library rather than opening one per command or per request.
- Size the pool to the concurrency you actually need (roughly, the number of Redis calls you expect in flight at once), not to the number of application threads — most threads are not calling Redis at every instant.
- Multiply pool size by the number of application instances and compare against
maxclientsbefore deploying; leave headroom for replicas, monitoring tools, and manualredis-clisessions. - Use
CLIENT SETNAME(or your client library’s equivalent connection-naming option) so pooled connections are identifiable inCLIENT LISTduring an incident. - Periodically check
INFO clientsin production dashboards to catch a leaking pool (a steadily risingconnected_clients) before it exhaustsmaxclients. - Configure a reasonable idle timeout on the pool so long-unused connections are recycled instead of accumulating stale sockets, especially across failovers.
- Keep pub/sub connections in a separate pool (or dedicated connections) from regular command connections — a connection blocked waiting on a pub/sub message shouldn’t also be relied on to serve ordinary commands.
Practice Exercises
- Open two separate
redis-clisessions to the same server. In each, runCLIENT SETNAMEwith a different name, then runCLIENT LISTfrom one of them and confirm you can see both connections and both names. - Run
INFO clientsand note theconnected_clientsvalue. Open three moreredis-clisessions (without closing the first), then runINFO clientsagain from any of them and confirm the count increased by three. - Run
CONFIG GET maxclients. Suppose you plan to deploy 30 instances of an application, each wanting a pool of up to 20 connections. Work out whether that fits under the ceiling, and if not, what pool size per instance would.
Summary
- Every Redis command travels over a connection; opening a new one per command wastes time on repeated TCP/TLS/AUTH handshakes.
- Connection pooling keeps a set of connections open and reused, which is why virtually every Redis client library provides a pool.
- Redis executes commands from all connections one at a time on a single thread, which is why each command is atomic — but also why one connection can only serve one caller at a time.
CLIENT ID,CLIENT SETNAME/GETNAME,CLIENT INFO, andCLIENT LISTlet you name and inspect individual connections.INFO clientsandCONFIG GET maxclientstell you how many connections are open and how many the server allows — the two numbers you need to size pools safely.- Undersized reconnect-per-command patterns hurt latency; oversized pools across many app instances can exceed
maxclientsand start failing withERR max number of clients reached.
