Rooms live in the memory of one process. The moment you run two — cluster (Clustering), pm2 29,762 , containers behind a load balancer — a broadcast reaches only the clients connected to the process that sent it, and half your users see nothing. Two separate pieces fix that. An adapter relays broadcasts between processes: @socket.io/redis-adapter 8.3.0 over Redis 2,763 pub/sub with two ioredis 6.0.0 15,343 clients, @socket.io/redis-streams-adapter 0.3.1, @socket.io/postgres-adapter 0.5.0, or @socket.io/cluster-adapter 0.3.0 for workers on one host, which needs no extra service and is the one below. Sticky sessions keep a client's long-polling requests on the process that owns its session; without them a polling client gets Session ID unknown and reconnects forever. WebSocket-only clients do not need stickiness, but you cannot assume every client gets a WebSocket.
if (cluster.isPrimary) {
const httpServer = createServer();
setupMaster(httpServer, { loadBalancingMethod: 'least-connection' });
setupPrimary(); // relays broadcasts between workers
cluster.setupPrimary({ serialization: 'advanced' }); // required: sends binary over IPC
httpServer.listen(4141);
for (let i = 0; i < 2; i++) cluster.fork();
} else {
const io = new Server(createServer(), { adapter: createAdapter() });
setupWorker(io); // the primary hands this worker connections
io.on('connection', (socket) => {
socket.join('book:978-0441013593');
console.log(`worker ${process.pid} accepted ${socket.id}`);
socket.on('price', (p) =>
io.to('book:978-0441013593').emit('price', { ...p, from: process.pid }));
});
}Two clients connect a second apart, landing on different workers; one then emits:
$ node cluster.js $ node cluster-client.js worker 43488 accepted DLhDqvSEjJB7gVcVAAAA a got $12.99 from worker 9600 worker 9600 accepted IaqVDaxvg4G_b2n2AAAA b got $12.99 from worker 9600
Client a was on worker 43488 and still received what worker 9600 broadcast: the adapter carried it. Leave out cluster.setupPrimary({ serialization: 'advanced' }) and both clients fail with xhr poll error — a first-attempt mistake worth remembering. Across hosts, swap createAdapter() for the Redis adapter (Server-Side Caching with Redis) and configure stickiness at the proxy: nginx 75 ip_hash, or sticky cookies on a load balancer. Plan the restart too: a rolling deploy drops every socket at once, so raise reconnectionDelayMax on the client.