Lesson 9 / 25
Scaling WebSockets and Server-Sent Events
Fan out live updates across many WebSocket servers with Redis.
A backplane for real-time servers
A single WebSocket or SSE server can push messages only to users connected to it. With many servers behind a load balancer, a message for user 42 must reach whichever server holds user 42's connection. Redis is a common backplane: every real-time server subscribes to relevant channels (per user, per room or per topic), and any service publishes updates to Redis, which fans them out to all servers; each server forwards to its own connected clients. Frameworks build on this: the Socket.IO Redis adapter (including a sharded variant for Redis 7), SignalR's Redis backplane in ASP.NET Core, and Spring with Redis message listeners. Because Pub/Sub is fire-and-forget, design clients to resynchronise after reconnecting, for example by fetching missed notifications from an API, or by reading recent entries from a Redis Stream that also stores the updates.
A Node.js WebSocket server using Redis as a backplane
Each server subscribes once and forwards messages to its local sockets for that user.
import { createClient } from 'redis';
import { WebSocketServer } from 'ws';
const sub = createClient({ url: process.env.REDIS_URL });
await sub.connect();
const wss = new WebSocketServer({ port: 8080 });
const socketsByUser = new Map(); // userId -> Set of sockets on THIS server
wss.on('connection', (ws, req) => {
const userId = authenticate(req); // e.g. from a token
if (!socketsByUser.has(userId)) socketsByUser.set(userId, new Set());
socketsByUser.get(userId).add(ws);
ws.on('close', () => socketsByUser.get(userId)?.delete(ws));
});
await sub.pSubscribe('notify:user:*', (message, channel) => {
const userId = channel.split(':')[2];
for (const ws of socketsByUser.get(userId) ?? []) ws.send(message);
});
// any service: await redis.publish(`notify:user:${userId}`, JSON.stringify(payload));Switchboard operators in different buildings
Each building has an operator connected to its own phones. A central announcement system (Redis) tells every operator about a call for extension 42; only the operator who has extension 42 actually rings it.
Quick check: Why do multiple WebSocket servers need a backplane such as Redis Pub/Sub?
- WebSockets cannot send JSON
- Redis encrypts WebSocket traffic
- A message must reach whichever server holds the target user's connection
- Load balancers cannot route WebSockets
Answer
A message must reach whichever server holds the target user's connection — The backplane fans messages out so the server holding the right connection can deliver them.