Skip to content
Navigation

Type at least two characters. Search covers page titles, headings, tags and database names.

↑ ↓ to navigateEnter to openEsc to close0 pages
Redis and Valkeybeginner

Redis Pub/Sub

Fire-and-forget messaging in Redis, its delivery semantics, and when to use Streams instead.

2 min readBeginnerUpdated Edit this page

Pub/Sub delivers a message to every client currently subscribed to a channel.

SUBSCRIBE shop:events:orders
PUBLISH shop:events:orders '{"order_id":4711,"status":"paid"}'
 
PSUBSCRIBE shop:events:*        # pattern subscription

Delivery semantics

That makes it suitable for genuinely ephemeral signals — cache invalidation hints, live presence updates, dashboard ticks — and unsuitable for anything where a lost message matters.

For durable messaging with consumer groups and acknowledgements, use Streams.

Operational hazards

Slow subscribers consume server memory. Redis buffers outgoing messages per client. A subscriber that reads slower than the publish rate causes its output buffer to grow.

# class     hard limit  soft limit  soft seconds
client-output-buffer-limit pubsub 32mb 8mb 60

When a limit is exceeded, Redis disconnects that client — which is the intended protection, and means subscribers must handle reconnection and accept that they missed messages while away.

Publishing costs scale with subscriber count. A publish to a channel with a thousand subscribers writes a thousand times, on the single command thread.

Pattern subscriptions are more expensive, because every publish is matched against every pattern.

Cluster behaviour

In Redis Cluster, plain PUBLISH is broadcast across the whole cluster, which does not scale with node count. Redis 7 added sharded pub/sub (SPUBLISH / SSUBSCRIBE), where the channel is hashed to a slot and the message stays on the owning shard. Use the sharded form in cluster deployments where message volume is significant.

A reasonable use: cache invalidation

# Writer, after updating the system of record.
r.publish("shop:cache:invalidate", json.dumps({"key": "shop:product:882"}))
 
# Each application instance clears its local in-process cache.
for message in pubsub.listen():
    local_cache.pop(json.loads(message["data"])["key"], None)

This is a correct use precisely because a missed message is tolerable: the local entry simply expires by its own TTL a little later. Design any pub/sub consumer so that missing a message degrades freshness rather than correctness.