Scaling WebSockets
📈 Scaling WebSockets
rooms Map lives entirely in one server process's memory. The moment there's more than one server instance — for capacity, or just redundancy — that structure stops being enough. This chapter is about the specific problem persistent connections create that stateless HTTP never had to solve.
Why Stateless HTTP Doesn't Have This Problem
An ordinary HTTP request carries no memory of any previous request — a load balancer can send it to any healthy backend server, because every server is equally able to handle it from scratch. A WebSocket connection is the opposite: once established, it's a single, long-lived, stateful connection to one specific server process. That client is stuck to that one instance for as long as the connection lives.
📌 Sticky Sessions
A load balancer needs to route the same client to the same backend server for the entire life of a connection — not just for the initial handshake, but for Socket.IO's long-polling fallback too, since that makes repeated HTTP requests that all need to land on the same instance to work correctly.
How It's Usually Done
Consistent hashing on client IP, or a cookie the load balancer sets and reads on every request — either way, the same client keeps landing on the same backend.
The Real Cost
Load can get lopsided if some connections are long and heavy, and restarting one server instance drops every connection stuck to it, all at once — sticky sessions trade even load distribution for connection stability.
Why Rooms Break Across Servers
If User A is connected to Server 1 and User B to Server 2, and both are meant to be in the same chat room, Server 1's local rooms Map (from Chapter 6) has no way to reach User B's socket — that socket object only exists in Server 2's memory. Broadcasting "locally" is no longer broadcasting to the whole room.
📡 Redis Pub/Sub as the Fix
Instead of each server trying to reach sockets it doesn't have, every server subscribes to a Redis channel per room. When a server needs to broadcast, it publishes to Redis rather than only looping its own local Map — every subscribed server (including the one that published) receives the message and forwards it to whichever of its own sockets are in that room:
Before and After Redis
Chapter 6: Single Server
One rooms Map, one process — every member of a room is guaranteed to be reachable locally.
This Chapter: Multiple Servers
Each server still has its own local rooms Map for its own connections, bridged to every other server's Map via Redis pub/sub — a message published on one instance reaches members on all of them.
What This Buys
Horizontal scaling (add more server instances as connection count grows) and redundancy — losing one instance no longer means losing an entire room's ability to communicate.
What It Costs
Redis becomes a critical dependency every server needs, a small added latency per broadcast, and one more moving part that can itself fail or need scaling.
Socket.IO ships an official Redis adapter that implements exactly this pattern — another example of the "built in vs hand-rolled" trade-off from Chapter 4, this time applied to scaling instead of rooms themselves.
💻 Coding Challenges
Challenge 1: Explain Why Sticky Sessions Matter for Fallback
Explain specifically why Socket.IO's long-polling fallback (Chapter 4) requires sticky sessions even when the WebSocket transport itself might not strictly need them for a single request.
Goal: Practice connecting the fallback mechanism from Chapter 4 to the sticky-session requirement from this chapter.
Challenge 2: Wire Up Redis Pub/Sub for Presence
Rewrite Chapter 6's presence "joined"/"left" broadcasts (originally calling sendToRoom directly) to instead publish through Redis, so presence updates are visible to members connected to any server instance.
Goal: Practice applying the publish/subscribe bridge to code already written in an earlier chapter.
Challenge 3: Diagnose a Missed Broadcast
A server instance was briefly restarting when another server published a room message via Redis. Explain what happens to that specific message for clients connected to the restarting server, and why.
Goal: Practice reasoning about pub/sub's lack of message persistence.
Redis pub/sub is fire-and-forget — a message published to a channel is delivered only to subscribers currently connected at that exact moment. If a server instance is restarting, briefly disconnected from Redis, or hasn't subscribed yet, any message published during that window is simply gone for it, permanently — there's no replay, no queue, no persistence to catch up on later. This is a fundamentally different guarantee than a proper message queue (with durable storage and delivery guarantees), and it's the right trade for ephemeral real-time broadcasts like chat or presence, but the wrong tool entirely for anything that needs guaranteed delivery.
🎯 What's Next
With scaling handled, the next chapter turns to a topic that's been deferred since Chapter 3: Authentication & Security for Real-Time Connections — authenticating at the handshake, checking origin, rate limiting, and validating every incoming message.