Pub/Sub Messaging
How Pub/Sub Works
Redis Pub/Sub is a fire-and-forget messaging pattern. Publishers send messages to channels; subscribers receive messages in real time. There is no message persistence — if no subscriber is listening, the message is lost.
Publisher → Channel → Subscriber(s)
↓
(no persistence, no ACK)
Pub/Sub messages do not consume keyspace memory — they pass through the server without being stored.
Basic Publish/Subscribe
Open two terminals:
# Terminal 1 — Subscriber
redis-cli
SUBSCRIBE news:sports news:tech
# Terminal 2 — Publisher
redis-cli
PUBLISH news:sports "Team wins championship"
PUBLISH news:tech "New framework released"
Subscribers only receive messages published after they subscribe — no message history or replay.
Pattern Subscribe
Subscribe to channels matching glob patterns:
PSUBSCRIBE news:*
PUBLISH news:local "City council vote"
PUBLISH news:world "Election results"
PSUBSCRIBE matches channels with glob patterns (*, ?, [abc]).
Unsubscribe
UNSUBSCRIBE news:sports
PUNSUBSCRIBE news:*
Application Example
import json
import redis
r = redis.Redis(decode_responses=True)
# Publisher
r.publish("orders:created", json.dumps({"order_id": 42, "total": 99.99}))
# Subscriber (blocking loop)
pubsub = r.pubsub()
pubsub.subscribe("orders:created")
for message in pubsub.listen():
if message["type"] == "message":
channel = message["channel"]
data = json.loads(message["data"])
handle_order(data)
Thread-Safe Subscriber Pattern
Run subscribers in dedicated threads or async tasks — the listen loop blocks.
import threading
def subscriber_thread():
pubsub = r.pubsub()
pubsub.subscribe("notifications")
for msg in pubsub.listen():
if msg["type"] == "message":
process(msg["data"])
threading.Thread(target=subscriber_thread, daemon=True).start()
Use Cases
| Pattern | Example |
|---|---|
| Live notifications | Chat messages, alerts |
| Cache invalidation | Broadcast invalidate:user:1001 to all app servers |
| Microservice events | Order status updates between services |
| Real-time dashboards | Metric push to WebSocket layer |
| Config reload | Signal all workers to refresh settings |
Cache Invalidation via Pub/Sub
# Service A — after update
redis.publish("cache:invalidate", json.dumps({"keys": ["user:1001", "users:list"]}))
# All app servers — subscriber
def on_invalidate(message):
keys = json.loads(message)["keys"]
local_cache.delete_many(keys)
Pub/Sub vs Streams
| Feature | Pub/Sub | Streams |
|---|---|---|
| Persistence | No | Yes (stored in memory) |
| Consumer groups | No | Yes |
| Replay | No | Yes (XREAD from offset) |
| At-least-once delivery | No | Yes (with XACK) |
| Latency | Lowest | Low |
| Backpressure | None — slow consumers lose messages | PEL tracks pending |
Choose Pub/Sub when: real-time delivery, message loss acceptable, lowest latency required.
Choose Streams when: durability, replay, consumer groups, or at-least-once semantics needed.
Sharded Pub/Sub (Redis 7+ / Cluster)
Standard Pub/Sub in Cluster broadcasts to all nodes — inefficient at scale. Sharded Pub/Sub routes messages only to nodes with relevant slot subscribers.
SPUBLISH channel:shard1 "message"
SSUBSCRIBE channel:shard1
Cluster clients must use sharded pub/sub APIs for correct routing in cluster mode.
Monitoring Pub/Sub
PUBSUB CHANNELS # active channels
PUBSUB NUMSUB news:sports news:tech
PUBSUB NUMPAT # pattern subscription count
CLIENT LIST # identify subscribers
High pubsub_channels count may indicate channel sprawl — consolidate where possible.
Best Practices
- Use structured message payloads (JSON with schema version)
- Keep messages small — Pub/Sub is not for large file transfer
- Implement reconnection logic in subscribers — connections drop
- Do not rely on Pub/Sub for critical business events without Streams backup
- Use specific channel names (
orders:created) over generic (events)
Common Mistakes
| Mistake | Impact |
|---|---|
| Expecting message persistence | Lost messages during downtime |
| No subscriber reconnection | Silent message loss after network blip |
| Publishing huge payloads | Blocks client buffers, latency spikes |
| One channel for all events | Subscribers receive irrelevant traffic |
| Pub/Sub for task queues | No ACK, no retry — use Lists or Streams |
Troubleshooting
Subscriber receives nothing:
PUBSUB NUMSUB mychannel
# 0 subscribers? Check channel name exact match (case-sensitive)
# Subscriber connected to different Redis instance?
Messages stop after a while:
CLIENT LIST
# Subscriber connection timed out? Implement heartbeat/reconnect
CONFIG GET timeout
High CPU with many pattern subscriptions:
PUBSUB NUMPAT
# PSUBSCRIBE * patterns are expensive — use specific channels
Performance Tips
- Batch notifications when possible — one message with array vs 100 messages
- Separate Pub/Sub Redis instance from cache instance to isolate load
- Use Sharded Pub/Sub in Cluster for channel counts > 1000
- Avoid
PSUBSCRIBE *— prefer explicit channel lists
Production Scenario
A live sports score app pushed score updates via Pub/Sub to 200 WebSocket server instances. Each server subscribed to scores:{sport} channels. Average delivery latency was 1.2ms. During a network partition, 30 seconds of updates were lost — the team added Streams as a backup for score persistence and replay on reconnect, while keeping Pub/Sub for live fan-out.
Pub/Sub is simple and fast — ideal for real-time fan-out where occasional message loss is acceptable. Choose Streams when durability and delivery guarantees matter.