Loading
← All postsBackend, August 18, 2026

What 600k concurrent sessions taught me about Redis connections

Pool exhaustion rarely starts in Redis itself. It starts in how the application opens connections. Here's the multiplexing approach behind keeping agent apps up at peak.

Filed under.NET, Redis, Performance and 6 min read

During peak traffic on the telecom agent apps I worked on at DPL, the Redis layer started failing in a way that looked like a capacity problem. It wasn't. With 600k+ concurrent sessions, the application was running out of connections long before Redis ran out of headroom.

Why connection pools run dry

Most Redis clients are cheap to call and expensive to connect. Every new TCP connection costs a handshake, authentication, and a slot on the server. When connections scale with traffic instead of staying flat, nobody notices at normal load. At peak, requests queue for a connection, time out, and retry, which makes the queue even longer. The usual culprits:

  • Creating a client per request, or per scoped service
  • Blocking calls like .Result and .Wait() that pin threads while they wait on Redis
  • Heavy, long-running commands sharing connections with hot-path reads

Multiplexing instead of pooling

In .NET, StackExchange.Redis is built around a multiplexer: one long-lived connection object shared by many concurrent operations. Commands are pipelined over the same socket, so thousands of callers don't need thousands of connections. The fix is less about tuning a pool and more about treating the connection as application-wide infrastructure.

// Program.cs: one multiplexer for the whole app
builder.Services.AddSingleton<IConnectionMultiplexer>(_ =>
    ConnectionMultiplexer.Connect(builder.Configuration["Redis:ConnectionString"]!));

// Anywhere else: borrow a lightweight database handle
public sealed class SessionStore(IConnectionMultiplexer redis)
{
    private readonly IDatabase _db = redis.GetDatabase();

    public Task<RedisValue> GetAsync(string sessionId) =>
        _db.StringGetAsync($"session:{sessionId}");
}

The principles behind the fix

  • One multiplexer per process, registered as a singleton and created at startup rather than on the first request.
  • Async all the way down, so a slow round trip never holds a thread hostage.
  • Keep heavy work off the hot path, so bulk operations can't starve session reads.
  • Watch connection counts, not just latency. A flat connection graph during a traffic spike is the signal that the fix is working.

The takeaway

When a cache falls over under load, look at how the application talks to it before scaling the cache. The answer is often not a bigger Redis, but fewer, shared connections, used asynchronously.

keep readingkeep reading
keep in touchkeep in touch

New notes go up here first, then on LinkedIn.

Short write-ups on problems I run into while building payment and backend systems. Follow along, or tell me what you'd like me to write about next.

Let's talk