Real-Time Backend
Redis Pub/Sub
An app runs on one server and everything is fine. They launch a second server to scale, and half the users stop receiving each other's messages. Redis Pub/Sub is the standard fix.
- **Socket.io Redis Adapter** is used in production apps to sync WebSocket events between cluster nodes. `io.emit()` on any server reaches every client
- **Slack** used Redis Pub/Sub for distributed presence: a `user-online` event publishes to a channel, and every workspace server instantly updates the member's status
- **Cache invalidation**: when data changes, one service publishes a `cache:invalidate:product-42` event, and every other node drops its local cache without direct calls between services
How Redis Pub/Sub works
Redis Pub/Sub is a publish-subscribe messaging mechanism. One process publishes a message to a **channel**, every subscribed process receives it immediately. Redis acts as the broker: it does not keep history and does not guarantee delivery. A message lives just long enough to reach the subscribers.
Key property: **ephemeral**. If a subscriber is offline at publish time, the message is lost forever. Pub/Sub is an event bus, not a queue with storage.
- **SUBSCRIBE channel**: subscribe to a specific channel
- **PUBLISH channel message**: publish a message
- **UNSUBSCRIBE channel**: unsubscribe
- **PSUBSCRIBE pattern**: subscribe by wildcard pattern (`chat:*`)
Server A publishes an event to a Redis channel. At that moment server B is restarting. What happens to the event?
Multiple WebSocket servers problem
Scenario: a Socket.io app runs on two servers behind a load balancer. Alice is connected to server-1, Bob to server-2. When server-1 calls `io.emit('message', data)`, the message goes only to clients on server-1. Bob receives nothing.
Classical horizontal-scaling problem for stateful servers. Each WebSocket server knows only its own connections. A shared sync channel between nodes is needed. Redis Pub/Sub fits this role perfectly.
Socket.io cluster of 3 servers. `socket.to('room-X').emit('update')` is called on server-1. A client in room-X is connected to server-3. What is needed for correct delivery?
Socket.io Redis Adapter
`@socket.io/redis-adapter` is the official adapter that replaces Socket.io's built-in in-memory adapter. On broadcast operations it publishes the event to Redis, every server is subscribed to the shared channel and delivers the event to its local clients.
Two separate connections are required: Redis does not allow one connection for both PUBLISH and SUBSCRIBE. A client in subscribe mode only accepts SUB/UNSUB commands.
Slack used Redis Pub/Sub to sync presence between servers: when a user signs in, an event is published to a `presence:user-id` channel, and every node serving their workspace updates the status display in real time.
- **io.emit()**: every client on every node
- **io.to('room').emit()**: every client of the room on every node
- **socket.broadcast.emit()**: everyone except the sender, on every node
- **socket.emit()**: a single client, no adapter needed
Why does the Redis Adapter need two separate Redis connections, pubClient and subClient?
Channel patterns and limits
Redis Pub/Sub supports **wildcard subscriptions** via `PSUBSCRIBE`. The pattern `chat:*` subscribes to every channel like `chat:room-1`, `chat:room-42`, and so on. Useful for routing events by namespace without listing channels explicitly.
Key limitations of Redis Pub/Sub that decide when to use it and when not to:
- **No persistence**: messages are not stored, a late subscriber gets nothing
- **No delivery acknowledgement**: cannot tell whether subscribers received the message
- **No server-side filtering**: every subscriber gets every message of the channel
- **No priorities**: every message has equal weight
- **For queues**: use Redis Streams or BullMQ on top of List
Redis Pub/Sub is ideal for **fire-and-forget** scenarios: syncing caches between nodes, broadcasting presence events, CDN invalidation. For reliable delivery with guarantees, use Redis Streams (with `XREAD` and consumer groups).
Redis Pub/Sub and Redis Streams are the same thing, just different APIs
Pub/Sub is an ephemeral event bus with no storage. Streams is a persistent log with consumer groups and delivery acknowledgement
A Pub/Sub message lives for milliseconds and disappears after delivery. A Stream record lives until explicitly deleted and can be re-read. Intrinsicly different reliability guarantees
A notification system: the user must receive a push notification exactly once, even if they were offline. Does Redis Pub/Sub fit?
Summary
- **Redis Pub/Sub is ephemeral**: no persistence, no acks. Only delivery to active subscribers in real time
- **Two Redis connections are required** for the adapter: subscriber mode blocks every non-SUB/UNSUB command on a single connection
- **PSUBSCRIBE with wildcard patterns** (`chat:*`, `notifications:user:*`) subscribes to groups of channels without listing them explicitly
- **For guaranteed delivery** use Redis Streams or BullMQ. Pub/Sub is only for fire-and-forget sync
Related topics
Redis Pub/Sub is part of a wider real-time and scaling ecosystem:
- Redis Streams — Alternative with persistence and consumer groups for reliable delivery
- WebSocket horizontal scaling — The problem the Redis Adapter solves: syncing stateful connections
- BullMQ — Job queue on top of Redis with delivery guarantees and retry logic
- Socket.io rooms and namespaces — Socket.io abstractions that the Redis Adapter distributes across cluster nodes
Вопросы для размышления
- When in a project does Redis Pub/Sub make more sense than direct HTTP calls between services for sync?
- How does the Socket.io app architecture change when moving from one server to a 5-node cluster?
- What happens to a real-time chat if Redis is down for 30 seconds, and how do you handle that gracefully?