One way to study the fundamentals of system design is to pick any two generic features and look at how they intersect.
In this post: long-lived connections x load balancing.
To make it concrete, let's consider a chess website where players connect with WebSockets to send and receive moves in real time.
For any active game on the site, we have a "GameSession" data structure with all the info, like the IDs of the two players.
With multiple servers behind one or more load balancers, the key decision is: "Where is the source of truth for this state?" Many other decisions are downstream of this.
Keeping GameSession instances in memory on one of the servers adds complexity, such as: how can two users play if they are mapped to different servers by the load balancers?
Lichess hosts millions of games daily, so I looked at how they approach this problem.
Their solution is to use Redis to bypass much of this complexity.
That is: the GameSession data structures can live in Redis, and any backend can read and write to them. Backends can hold in-memory copies for convenience, but the copies should not affect correctness.
Our ideal system is one where *all* the components except Redis are replaceable: stateless load balancers, backend servers with only "soft" (non-authoritative) state, and ephemeral client connections.
- If a load balancer dies, clients talk to a different one and establish a new connection. Even if they are mapped to a different server, the server can fetch the GameSession data structure from Redis.
- If a server dies, all connections to it die, but the GameSessions survive in Redis. The clients hit the load balancers again, which map them to a different server.
- If a client loses internet for a second, they can reconnect and reach the same or a different backend.
In all three cases, the user experiences a momentary disconnect and can continue playing.
A key requirement is that the GameSession data structures in Redis are "keyed" by something like user ID, *not* a connection/WS ID (I wrote more about this in a post linked in the comments).
The cost of using Redis is extra latency, as backends usually talk to it over the network. In-memory state is much faster.
This trade-off is worth it in the case of Lichess, where state updates are small and move frequency is relatively low.
Post about ephemeral but long-lived connections: x.com/Nil053/status/2014085824019931507