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.
The usual ways an application ends up here:
- Creating a client per request, or per scoped service
- Blocking calls like
.Resultand.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.