Pub Sub
Redis Pub/Sub messaging patterns for real-time event broadcasting
You are an expert in Redis Pub/Sub for building real-time messaging and event broadcasting systems.
## Key Points
- **Use separate Redis clients** for subscriptions and regular commands. A subscribed client is blocked from issuing non-subscribe commands.
- **Keep messages small.** Pub/Sub messages pass through the Redis event loop. Large payloads (>1 KB) at high volume degrade server throughput.
- **Use pattern subscriptions sparingly.** Each incoming message is matched against every active pattern, adding CPU overhead proportional to pattern count.
- **Namespace channels** with a consistent convention (e.g., `service:entity:event`) to avoid collisions and simplify pattern subscriptions.
- **Handle reconnection.** When a subscriber disconnects and reconnects, it must resubscribe. ioredis handles this automatically if `autoResubscribe` is enabled (default).
- **Assuming message durability.** Pub/Sub is fire-and-forget. If no subscriber is listening, the message vanishes. For durable messaging, use Redis Streams or an external message broker.
- **Using a single connection for subscribe and commands.** This causes errors or silently dropped commands. Always use a dedicated connection for subscriptions.
- **Blocking the message handler.** If `sub.on("message", ...)` performs slow async work, message processing backs up. Offload heavy processing to a worker queue.
- **Not monitoring `subscription` count.** If pattern subscriptions grow unbounded (e.g., one per user), Redis CPU usage spikes. Check `PUBSUB NUMPAT` periodically.
- **Ignoring the subscriber count return from PUBLISH.** A return of 0 means nobody received the message, which may indicate a deployment or connectivity issue.skilldb get redis-skills/Pub SubFull skill: 177 linesPub/Sub — Redis
You are an expert in Redis Pub/Sub for building real-time messaging and event broadcasting systems.
Core Philosophy
Overview
Redis Pub/Sub provides a fire-and-forget messaging system where publishers send messages to channels and subscribers receive them in real time. Messages are not persisted; if a subscriber is offline when a message is published, that message is lost. This makes Pub/Sub ideal for ephemeral notifications, real-time updates, and inter-service signaling where at-most-once delivery is acceptable.
Core Concepts
Channels
Named channels act as message topics. Publishers send to a channel name; all active subscribers on that channel receive the message. Channel names are arbitrary strings, commonly using colon-separated namespaces (e.g., notifications:user:42).
Pattern Subscriptions
Subscribers can use glob-style patterns (PSUBSCRIBE) to receive messages from all channels matching a pattern, such as notifications:*.
Dedicated Connections
A Redis connection in subscribe mode cannot issue other commands. Applications must use separate Redis client instances for subscribing and for normal operations.
Message Ordering
Messages on a single channel are delivered in the order they were published. There is no ordering guarantee across channels.
Implementation Patterns
Basic publish and subscribe
import Redis from "ioredis";
// Subscriber — must be a dedicated connection
const sub = new Redis();
// Publisher — separate connection
const pub = new Redis();
sub.subscribe("events:order-created", (err, count) => {
if (err) throw err;
console.log(`Subscribed to ${count} channel(s)`);
});
sub.on("message", (channel, message) => {
const event = JSON.parse(message);
console.log(`[${channel}] Order ${event.orderId} created`);
});
// Publish from another part of the application
await pub.publish(
"events:order-created",
JSON.stringify({ orderId: "abc-123", total: 59.99, ts: Date.now() })
);
Pattern subscription
const sub = new Redis();
sub.psubscribe("events:*", (err) => {
if (err) throw err;
});
sub.on("pmessage", (pattern, channel, message) => {
// pattern = "events:*"
// channel = "events:order-created" (the actual channel)
console.log(`Pattern ${pattern} matched channel ${channel}`);
handleEvent(channel, JSON.parse(message));
});
Chat room with multiple channels
class ChatService {
private sub: Redis;
private pub: Redis;
constructor() {
this.sub = new Redis();
this.pub = new Redis();
}
async joinRoom(roomId: string, onMessage: (msg: ChatMessage) => void) {
const channel = `chat:room:${roomId}`;
await this.sub.subscribe(channel);
this.sub.on("message", (ch, raw) => {
if (ch === channel) {
onMessage(JSON.parse(raw));
}
});
}
async sendMessage(roomId: string, userId: string, text: string) {
const msg: ChatMessage = { userId, text, ts: Date.now() };
await this.pub.publish(`chat:room:${roomId}`, JSON.stringify(msg));
}
async leaveRoom(roomId: string) {
await this.sub.unsubscribe(`chat:room:${roomId}`);
}
}
Cache invalidation broadcast
// When any app instance updates a cached entity, broadcast invalidation
async function invalidateCache(entityType: string, entityId: string) {
// Delete locally
localCache.delete(`${entityType}:${entityId}`);
// Notify all other instances
await pub.publish(
"cache:invalidate",
JSON.stringify({ entityType, entityId })
);
}
// Every instance subscribes on startup
sub.subscribe("cache:invalidate");
sub.on("message", (channel, message) => {
if (channel === "cache:invalidate") {
const { entityType, entityId } = JSON.parse(message);
localCache.delete(`${entityType}:${entityId}`);
}
});
Extracting subscriber count
// PUBLISH returns the number of subscribers that received the message
const receiverCount = await pub.publish("events:deploy", JSON.stringify({ version: "2.1.0" }));
console.log(`Message delivered to ${receiverCount} subscriber(s)`);
// PUBSUB NUMSUB returns subscriber counts per channel
const counts = await pub.pubsub("NUMSUB", "events:deploy", "events:rollback");
// Returns: ["events:deploy", "3", "events:rollback", "0"]
Best Practices
- Use separate Redis clients for subscriptions and regular commands. A subscribed client is blocked from issuing non-subscribe commands.
- Keep messages small. Pub/Sub messages pass through the Redis event loop. Large payloads (>1 KB) at high volume degrade server throughput.
- Use pattern subscriptions sparingly. Each incoming message is matched against every active pattern, adding CPU overhead proportional to pattern count.
- Namespace channels with a consistent convention (e.g.,
service:entity:event) to avoid collisions and simplify pattern subscriptions. - Handle reconnection. When a subscriber disconnects and reconnects, it must resubscribe. ioredis handles this automatically if
autoResubscribeis enabled (default).
Common Pitfalls
- Assuming message durability. Pub/Sub is fire-and-forget. If no subscriber is listening, the message vanishes. For durable messaging, use Redis Streams or an external message broker.
- Using a single connection for subscribe and commands. This causes errors or silently dropped commands. Always use a dedicated connection for subscriptions.
- Blocking the message handler. If
sub.on("message", ...)performs slow async work, message processing backs up. Offload heavy processing to a worker queue. - Not monitoring
subscriptioncount. If pattern subscriptions grow unbounded (e.g., one per user), Redis CPU usage spikes. CheckPUBSUB NUMPATperiodically. - Ignoring the subscriber count return from PUBLISH. A return of 0 means nobody received the message, which may indicate a deployment or connectivity issue.
Anti-Patterns
Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.
Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.
Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.
Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.
Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.
Install this skill directly: skilldb add redis-skills
Related Skills
Caching Patterns
Cache-aside, write-through, and write-behind caching strategies with Redis
Data Structures
Redis core data structures including strings, hashes, sets, sorted sets, and lists
Lua Scripting
Lua scripting in Redis for atomic multi-step operations
Sentinel Cluster
Redis Sentinel and Cluster configurations for high availability and horizontal scaling
Streams
Redis Streams for durable event processing with consumer groups
Adversarial Code Review
Adversarial implementation review methodology that validates code completeness against requirements with fresh objectivity. Uses a coach-player dialectical loop to catch real gaps in security, logic, and data flow.