Pub/Sub & Scaling Real-Time Apps

Redis
Chapter 6 ยท Pub/Sub & Scaling Real-Time Apps

๐Ÿ“ก Pub/Sub & Scaling Real-Time Apps

web-sockets1-7 named a "Redis pub/sub adapter" as the fix for rooms breaking across multiple WebSocket servers, without teaching pub/sub itself. This chapter builds PUBLISH/SUBSCRIBE from first principles, then shows exactly how it solves that scaling problem.

The Problem: WebSockets Don't Scale Across Servers by Default

A WebSocket connection is tied to one specific server process. If a chat application runs on multiple server instances behind a load balancer, a client connected to Server A and a client connected to Server B are, as far as either server is concerned, on entirely separate islands โ€” Server A has no built-in way to know Server B's clients even exist, let alone deliver a message to them. This is exactly the "rooms break across servers" gotcha web-sockets1-7 flagged without solving.

PUBLISH & SUBSCRIBE

Redis pub/sub is straightforward: a client SUBSCRIBEs to a named channel; any client PUBLISHes a message to that channel; every currently subscribed client receives it immediately.

# Terminal 1 โ€” subscriber 127.0.0.1:6379> SUBSCRIBE chat:room1 Reading messages... # Terminal 2 โ€” publisher 127.0.0.1:6379> PUBLISH chat:room1 "hello everyone" (integer) 1 # Terminal 1 immediately receives: 1) "message" 2) "chat:room1" 3) "hello everyone"

Pub/sub is fire-and-forget: if no one happens to be subscribed at the moment a message is published, that message is simply gone โ€” never delivered, never stored. This is a fundamentally different guarantee from Chapter 7's queues, where messages persist until a consumer actually processes them.

Using Redis Pub/Sub to Bridge Multiple Servers

The fix for web-sockets1-7's scaling problem: every WebSocket server instance also subscribes to a shared Redis channel. When a client sends a chat message, the server it's connected to publishes that message to Redis โ€” and Redis fans it out to every subscribed server, including ones the original client never touched.

The Full Flow: Client A โ†’ Server B

  1. Client A sends a chat message over its WebSocket connection to Server A.
  2. Server A relays it to its own local clients and PUBLISHes it to a shared Redis channel, e.g. chat:room1.
  3. Redis delivers that published message to every subscriber โ€” including Server B, which also subscribed to chat:room1 on startup.
  4. Server B receives the message from Redis and relays it over its own WebSocket connections to Client B, who was never connected to Server A at all.

This is exactly what a "Redis pub/sub adapter" (Socket.IO's own included adapter is a real example) automates under the hood โ€” every server instance subscribes to the same channels, and Redis becomes the shared nervous system connecting servers that otherwise know nothing about each other.

Pub/Sub vs. Queues (Chapter 7)

Pub/Sub

Fire-and-forget, no persistence โ€” a message published with no subscriber listening is lost forever. Right for real-time broadcast where late delivery is meaningless anyway.

Queue (Lists / Streams)

Messages persist until a consumer processes them, even if no consumer is currently connected. Right when a message must eventually be handled, not just broadcast live.

CommandPurpose
SUBSCRIBE channelListen for messages on a channel
PUBLISH channel messageSend a message to every current subscriber of a channel
UNSUBSCRIBE channelStop listening to a channel
PSUBSCRIBE patternSubscribe to every channel matching a pattern (e.g. chat:*)
โš ๏ธ Gotcha: A Published Message With No Subscriber Is Lost, Not Queued

It's an easy mistake to assume a message published to a channel will still be there when a subscriber connects later โ€” it won't. If Server B's subscription drops (a restart, a network blip) at the exact moment a message is published, that message is simply gone for Server B, with no way to retrieve it afterward. For real-time broadcast where only "right now" matters (a live chat message, a live cursor position), this is fine โ€” but anything that genuinely needs to survive a disconnected consumer needs Chapter 7's queue-based approach instead, not pub/sub.

๐Ÿ’ป Coding Challenges

Challenge 1: Subscribe and Publish

Write the redis-cli commands needed for one client to subscribe to a channel called notifications, and for another client to publish the message "new order placed" to it.

Goal: Practice the basic SUBSCRIBE/PUBLISH pair.

โ†’ Solution

Challenge 2: Trace the Cross-Server Flow

Using this chapter's four-step flow, explain what happens when Client A (connected to Server A) sends a message, and Client B (connected to Server B) is the intended recipient โ€” assume both servers subscribe to the same Redis channel.

Goal: Practice tracing the full publish-and-fan-out sequence that bridges two otherwise-isolated servers.

โ†’ Solution

Challenge 3: Pub/Sub or Queue?

For each, say whether Redis pub/sub or a queue (Chapter 7) is the right fit, and why: (a) broadcasting a live "user is typing" indicator in a chat app, (b) processing a payment that must eventually succeed even if the payment worker was briefly offline.

Goal: Practice applying the fire-and-forget vs. persistent-until-processed distinction to realistic scenarios.

โ†’ Solution

๐ŸŽฏ What's Next

The next chapter is Redis as a Queue: Lists vs. Streams โ€” simple list-based queues, Redis Streams, and consumer groups, deepening node3-6's brief mention of Redis Streams.