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

  1. Use structured message payloads (JSON with schema version)
  2. Keep messages small — Pub/Sub is not for large file transfer
  3. Implement reconnection logic in subscribers — connections drop
  4. Do not rely on Pub/Sub for critical business events without Streams backup
  5. 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.