Publish/Subscribe Explained

Redis Pub/Sub (Publish/Subscribe) is a messaging pattern where publishers send messages to named channels without knowing who, if anyone, is listening, and subscribers listen to channels without knowing who is publishing. It is built directly into the Redis server as a set of commands — PUBLISH, SUBSCRIBE, PSUBSCRIBE — with no extra broker or queue required. It is ideal for real-time fan-out use cases like chat rooms, live notifications, and cache-invalidation broadcasts, but it is fundamentally different from a queue: messages are not stored anywhere, so anyone who isn’t actively subscribed at the moment a message is published simply never sees it.

Overview / How Pub/Sub works

Every Redis connection can act as a publisher, a subscriber, or both. When a client issues SUBSCRIBE channel-name, that connection is registered internally in a table Redis keeps in memory mapping channel names to the set of client connections subscribed to them. Because Redis is single-threaded, this registration and every subsequent PUBLISH lookup happens without any locking — a message can never be delivered to only half of the subscriber list, because no other command can interleave mid-operation.

When a client issues PUBLISH channel-name message, the server does a synchronous, in-process fan-out: it looks up every connection currently subscribed to that exact channel name (plus every connection subscribed to a pattern that matches it via PSUBSCRIBE), and pushes the message directly onto each of those connections’ output buffers. PUBLISH then returns an integer — the number of clients the message was delivered to. This all happens in one atomic step on the main thread; there is no intermediate queue, no persistence, and no acknowledgment from subscribers. If zero clients are subscribed when PUBLISH runs, the message is discarded forever and the command still returns 0 without error.

This "fire-and-forget, in-memory, no history" design is the single most important thing to understand about Pub/Sub: it is not a durable queue. If you need messages to survive a subscriber disconnecting, replay from a point in time, or consumer groups with acknowledgment, use a Redis Stream (XADD/XREAD) instead — that’s covered in its own section of this course. Pub/Sub exists purely for live, ephemeral broadcast.

Once a connection issues SUBSCRIBE or PSUBSCRIBE, that connection enters a special subscriber mode: in RESP2 it can no longer run ordinary data commands like GET or SET until it unsubscribes from every channel and pattern (RESP3 relaxes this by allowing other commands to interleave). This is why, in practice, subscriber connections are dedicated: an application opens one connection purely to listen, and uses separate connections to publish or to do regular data operations.

Pattern subscriptions via PSUBSCRIBE glob-pattern let one connection listen to many channels at once using glob-style wildcards (*, ?, [abc]). A message published to news:tech will reach both an exact subscriber of news:tech and a pattern subscriber of news:* — and if both are on the same connection, that connection receives the message twice, once as a message reply and once as a pmessage reply.

Syntax

PUBLISH channel message
SUBSCRIBE channel [channel ...]
UNSUBSCRIBE [channel ...]
PSUBSCRIBE pattern [pattern ...]
PUNSUBSCRIBE [pattern ...]
PUBSUB CHANNELS [pattern]
PUBSUB NUMSUB [channel ...]
PUBSUB NUMPAT
  • channel — an arbitrary string naming a broadcast topic; by convention on this site, namespaced with colons, e.g. news:tech, orders:new.
  • message — the payload string sent to every current subscriber of that channel.
  • pattern — a glob-style pattern (same syntax as KEYS) matched against channel names at publish time, e.g. news:*.
  • PUBSUB CHANNELS [pattern] — lists channels that currently have at least one subscriber, optionally filtered by a glob pattern.
  • PUBSUB NUMSUB [channel ...] — returns, for each named channel, the channel name followed by its current subscriber count.
  • PUBSUB NUMPAT — returns the total number of pattern subscriptions active on the server (not per-pattern counts).
Command Time complexity
PUBLISH O(N+M) — N subscribers of the channel, M patterns checked
SUBSCRIBE / UNSUBSCRIBE O(N) for N channels given in the call
PSUBSCRIBE / PUNSUBSCRIBE O(N) for N patterns given in the call
PUBSUB CHANNELS O(N) where N is the number of active channels
PUBSUB NUMSUB O(N) for N channels requested
PUBSUB NUMPAT O(1)

Examples

Example 1: Publishing with no subscribers

PUBLISH orders:new "order:5042 created"
EXISTS orders:new

Output:

(integer) 0
(integer) 0

PUBLISH returns 0 because no client is currently subscribed to orders:new — the message is simply dropped. The follow-up EXISTS also returns 0, proving an important point: channels are not keys. PUBLISH never writes anything into the keyspace, so a channel cannot be inspected with GET, listed with SCAN, or given a TTL; it only exists as a live registration in the subscriber table.

Example 2: A live subscriber receiving a message

In real use, Pub/Sub always involves two separate connections running at the same time — one blocked listening, one publishing. Open two redis-cli sessions side by side to see this for yourself.

Terminal 1 (the subscriber — this call blocks and streams messages as they arrive):

SUBSCRIBE news:tech

Output (as messages arrive):

1) "subscribe"
2) "news:tech"
3) (integer) 1
1) "message"
2) "news:tech"
3) "Redis 7.4 released with new features!"

Terminal 2 (the publisher, run right after Terminal 1 is subscribed):

PUBLISH news:tech "Redis 7.4 released with new features!"

Output:

(integer) 1

The subscriber’s first reply confirms the subscription itself (a three-element array: the type subscribe, the channel name, and the new total subscription count for that connection). Every message afterward arrives as a message reply containing the channel and payload. The publisher’s PUBLISH call returns 1, matching the one connection that was listening at that instant — timing matters, since a subscriber that connects a second later would never see this message.

Example 3: Pattern subscriptions with PSUBSCRIBE

PSUBSCRIBE news:*

Output:

1) "psubscribe"
2) "news:*"
3) (integer) 1
1) "pmessage"
2) "news:*"
3) "news:tech"
4) "Redis 7.4 released with new features!"

A pattern subscriber receives a four-element pmessage reply instead of a three-element message reply, because it needs to report which specific channel matched the pattern in addition to the pattern itself. A single PUBLISH news:tech ... call would fan out to every exact subscriber of news:tech and every pattern subscriber whose pattern matches it, all in the same atomic step.

How it works step by step

For a call like PUBLISH news:tech "hello", Redis performs these steps entirely on the single main thread before returning control:

  • Look up news:tech in the exact-channel subscriber table and collect every connection registered there.
  • Walk the list of active glob patterns from PSUBSCRIBE calls and test each one against news:tech, collecting matching connections.
  • For each collected connection, write a message (or pmessage) reply into that connection’s output buffer, to be flushed to the client on the next I/O pass.
  • Count the total number of connections notified and return that count as the integer reply.
  • Discard the message — nothing about it is written to disk, to an RDB snapshot, or to the AOF log, and no record of it exists a moment later.

Common Mistakes

Mistake: assuming a message is queued for subscribers who join late. A common bug is publishing a startup notification before the consumer service has finished subscribing, then wondering why it never arrives. Pub/Sub has no backlog — a subscriber only receives messages published after its SUBSCRIBE call completes. If you need late joiners to catch up, use a Stream with XADD/XREAD instead, which persists entries.

Mistake: issuing normal commands on a subscribed connection. Once a connection calls SUBSCRIBE, trying to run something like GET user:1001:name on that same connection fails, because in RESP2 a subscriber connection is restricted to subscribe-related commands until it unsubscribes:

redis-cli> SUBSCRIBE news:tech
(subscribed, connection now blocked/restricted)
redis-cli> GET user:1001:name
(error) ERR Can't execute 'get': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context

The fix is architectural: keep one dedicated connection purely for subscribing, and use a separate connection for ordinary reads and writes.

Mistake: relying on Pub/Sub for anything that must not be lost. If the network hiccups and a subscriber’s connection drops for even a second, every message published during that gap is gone permanently — there’s no redelivery, no offset to resume from, and no way to detect after the fact that something was missed. For billing events, order processing, or audit trails, use a Stream with consumer groups, not Pub/Sub.

Best Practices

  • Use Pub/Sub only for ephemeral, best-effort broadcasts — live chat, presence updates, cache-invalidation pings — where missing a message occasionally is acceptable.
  • Keep a subscriber connection dedicated to listening; never share it with a connection your application also uses for regular data commands.
  • Use namespaced, colon-separated channel names (chat:room:42, alerts:system) so PSUBSCRIBE patterns and PUBSUB CHANNELS filters stay predictable.
  • Reach for PSUBSCRIBE when the set of channels is dynamic or large (per-user or per-room channels) rather than issuing thousands of individual SUBSCRIBE calls.
  • Use PUBSUB CHANNELS, PUBSUB NUMSUB, and PUBSUB NUMPAT for monitoring and debugging — check whether anyone is actually listening before assuming a publish reached its audience.
  • For messaging that must be durable, replayable, or acknowledged, use Redis Streams instead of Pub/Sub.
  • Consider Redis keyspace notifications (enabled via CONFIG SET notify-keyspace-events, and covered in more detail elsewhere) if you want to publish automatically whenever keys expire or change, rather than publishing manually from application code.

Practice Exercises

  • Open two redis-cli sessions. In the first, subscribe to a channel named sports:scores. In the second, publish two different messages to it and confirm both arrive in the first session in order.
  • In one session, use PSUBSCRIBE to subscribe to sports:*. From another session, publish to both sports:scores and sports:news, and confirm the pattern subscriber receives both as pmessage replies with the correct channel name in each.
  • With no subscribers connected, run PUBLISH promo:daily "50% off" and predict the return value before running it. Then run PUBSUB CHANNELS and PUBSUB NUMPAT and explain why both come back empty even though you just published successfully.

Summary

  • Pub/Sub lets one connection PUBLISH a message to a named channel and every currently-subscribed connection receive it instantly, with no storage or replay.
  • PUBLISH returns the count of connections the message was delivered to; 0 means nobody was listening and the message is gone.
  • Channels are not keys — they never appear via EXISTS, SCAN, or TTL, since nothing is written to the keyspace.
  • PSUBSCRIBE matches glob patterns against channel names at publish time and delivers pmessage replies instead of message replies.
  • A subscribed connection is restricted to subscribe-related commands in RESP2, so dedicate separate connections to publishing, subscribing, and regular data access.
  • For durable, replayable, or acknowledged messaging, use Redis Streams instead of Pub/Sub.