Loading
← All notes

What 600k concurrent sessions taught me about Redis connections

Our Redis outages looked like a capacity problem. They weren't: every request was opening its own connection. Here's the multiplexing fix that kept the Mixx Tanzania app up at peak.

Filed under.NET, Redis and Performance

During peak traffic on the Mixx Tanzania app at DPL, the Redis layer started failing in a way that looked like a capacity problem: timeouts, runtime errors, and eventually downtime. It wasn't capacity. Every request to Redis was creating a new connection, so with 600k+ concurrent sessions the application went past the connection limit long before Redis ran out of headroom.

Why connections run out

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 longer still.

peak traffictime →open connectionsconnection limit×××timeoutsa connection per requestone multiplexed connection
Fig. 1Illustrative. With a connection per request, open connections follow traffic and cross the limit at peak, which is where the timeouts start. A multiplexed connection stays flat however busy it gets.

The usual ways an application ends up here:

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

Multiplexing instead of connecting

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

// Program.cs: one multiplexer for the whole app, created at startup
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 through a traffic spike is the sign the fix is working.

The takeaway

After the change, the timeout errors at peak disappeared and the app stayed up through its busiest periods. When a cache falls over under load, look at how the application talks to it before you scale the cache. The answer is often not a bigger Redis, but fewer, shared connections, used asynchronously.

the project behind itthe project behind it

Read the case study

ReliabilityDPL, 2024 – 2026

Redis Connection Multiplexing

In the Mixx Tanzania app, every Redis call was opening its own connection. At peak that went past the connection limit and caused downtime, so I replaced it with one shared, multiplexed connection.

600k+concurrent sessions without running out of connections

Built with.NET, Redis and Connection multiplexing

Read case study
keep readingkeep reading

More notes

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