Blog/Engineering

Scaling WebSocket rooms with Redis Pub/Sub: lessons learned

How we migrated from a single Socket.IO process to a Redis-backed multi-node cluster.

A

Arhama F.

Engineering at Collab.Code. Distributed systems, WebSockets, and CRDTs.

Apr 7, 2026

12 min read

The single-process bottleneck

With 500 concurrent rooms, a single Node.js Socket.IO server maxes out at ~85% CPU. Horizontal scaling is the obvious answer — but with WebSockets, you can't just put a load balancer in front. A client connected to Server A can't receive events emitted by Server B.

Redis Pub/Sub to the rescue

The solution: every server subscribes to a Redis channel per room. When Server A emits to room 'abc', it also publishes to Redis channel 'room:abc'. Server B (and C, D…) receives that message and fans it out to its own connected clients.

javascript
// When receiving a yjs:update from a client
socket.on('yjs:update', (payload) => {
  // Fan out locally
  socket.to(roomId).emit('yjs:update', payload)
  // Fan out to other nodes via Redis
  redis.publish(`room:${roomId}`, JSON.stringify(payload))
})

// On startup, subscribe to all room channels
redisSub.psubscribe('room:*', (channel, message) => {
  const roomId = channel.replace('room:', '')
  io.to(roomId).emit('yjs:update', JSON.parse(message))
})

Graceful degradation

We always keep an in-memory fallback. If Redis is unreachable on startup, the server logs a warning and continues with local-only fanout. This means a single-node deploy (like a dev environment) just works without Redis installed.

A

Arhama F.

Engineering at Collab.Code. Distributed systems, WebSockets, and CRDTs.

Related posts

Try Collab.Code

Real-time collaboration, AI review, and sandboxed execution — Pro $30/mo, Enterprise $100/mo.

Get started — $30/mo
Scaling WebSocket rooms with Redis Pub/Sub: lessons learned | Collab.Code