principletypescriptMajor
Fan-Out Patterns: Server-Side Rooms vs Pub-Sub Broker for Multi-Server Scale
Viewed 0 times
fan-outRedis pub-subhorizontal scalingWebSocket clusterroomsNATSmulti-server
Problem
A WebSocket server with in-memory rooms works on a single instance, but when scaled horizontally, clients connected to different server instances cannot receive messages meant for their room because each server has only local room state.
Solution
Use a pub-sub broker (Redis Pub/Sub, NATS) as the shared message bus between server instances. Each server subscribes to room topics and forwards messages to its locally connected clients.
import { createClient } from 'redis';
const pub = createClient();
const sub = createClient();
await pub.connect();
await sub.connect();
// Subscribe this server instance to a room
async function joinRoom(roomId: string, ws: WebSocket) {
localRooms.get(roomId)?.add(ws);
// Only subscribe Redis channel once per room per server
if (!redisSubscriptions.has(roomId)) {
await sub.subscribe(roomId, (message) => {
for (const client of localRooms.get(roomId) ?? []) {
if (client.readyState === WebSocket.OPEN) client.send(message);
}
});
redisSubscriptions.add(roomId);
}
}
// Publish to all servers
async function broadcastToRoom(roomId: string, data: unknown) {
await pub.publish(roomId, JSON.stringify(data));
}Why
In-memory rooms are local to the process. Redis Pub/Sub broadcasts a message to all subscribers across all server instances simultaneously. Each instance filters to only its local WebSocket clients for final delivery.
Gotchas
- Redis Pub/Sub has no persistence — a subscriber that is briefly disconnected misses messages. Use Redis Streams if replay is needed.
- Unsubscribe from Redis channels when the last local client leaves a room to avoid idle subscriptions.
- A single Redis Pub/Sub message is delivered to all subscribing servers, even those with no clients in that room.
- NATS JetStream and Kafka are better choices than Redis Pub/Sub for guaranteed delivery at high throughput.
Revisions (0)
No revisions yet.