Redis Cheatsheet

Pub/Sub

Use this Redis reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Command table

Pub/Sub is fire and forget. A PUBLISH is delivered to whoever is subscribed at that instant and then discarded. Nothing is stored, nothing is acknowledged, and there is no replay.

CommandWhat it doesComplexity
PUBLISH channel messageSend, return the number of clients that received itO(n+m)
SUBSCRIBE ch [ch ...]Subscribe to exact channel namesO(n) channels
UNSUBSCRIBE [ch ...]Leave channels, all of them with no argumentO(n)
PSUBSCRIBE pat [pat ...]Subscribe to glob patternsO(n) patterns
PUNSUBSCRIBE [pat ...]Leave patternsO(n)
SSUBSCRIBE ch [ch ...]Sharded subscribe (7.0+)O(n)
SUNSUBSCRIBE [ch ...]Leave sharded channelsO(n)
SPUBLISH channel messageSharded publish (7.0+)O(n)
PUBSUB CHANNELS [pattern]Active channels with at least one subscriberO(n)
PUBSUB NUMSUB [ch ...]Subscriber count per channelO(n)
PUBSUB NUMPATTotal pattern subscriptions across all clientsO(1)
PUBSUB SHARDCHANNELS [pattern]Active sharded channels (7.0+)O(n)
PUBSUB SHARDNUMSUB [ch ...]Subscribers per sharded channelO(n)

The integer PUBLISH returns is the count of receivers, so PUBLISH returning 0 means the message went nowhere and there is no error. That is the whole contract, and it is the one thing to internalize.

PUBLISH hu:notify:1042 '{"type":"mention","post":42}'   # (integer) 0, nobody home
PUBSUB CHANNELS                                          # (empty array)
PUBSUB NUMSUB hu:notify:1042                             # 1) "hu:notify:1042" 2) (integer) 0
PUBSUB NUMPAT                                            # (integer) 0

PUBSUB CHANNELS only lists channels with at least one exact subscriber, so a channel that only pattern subscribers are watching does not appear. Pattern subscriptions show up in NUMPAT instead.

Patterns

PSUBSCRIBE matches with the same glob syntax as KEYS: * any sequence, ? one character, [abc] a class, and \ escapes.

PSUBSCRIBE 'hu:notify:*'          # every user's channel
PSUBSCRIBE 'hu:*:errors'          # middle wildcard
PSUBSCRIBE 'hu:log:[wi]*'         # warn and info

A client subscribed to both hu:notify:1042 and hu:notify:* receives the same message twice, once as a message frame and once as a pmessage frame. Deduplicate or do not overlap.

Pattern matching runs for every published message against every registered pattern, so a thousand patterns multiply the cost of every single PUBLISH. Prefer many exact channels over broad patterns on a busy server.

The message protocol

A subscribed connection receives push frames rather than replies.

SUBSCRIBE hu:notify:1042
1) "subscribe"        2) "hu:notify:1042"   3) (integer) 1     <- confirmation, 3 is the count

1) "message"          2) "hu:notify:1042"   3) "{...}"         <- exact-channel delivery

1) "pmessage"  2) "hu:notify:*"  3) "hu:notify:1042"  4) "{...}"  <- pattern delivery
FrameFields
subscribe / unsubscribekind, channel, current subscription count
psubscribe / punsubscribekind, pattern, current count
ssubscribe / sunsubscribekind, shard channel, current count
messagekind, channel, payload
pmessagekind, pattern, channel, payload
smessagekind, shard channel, payload

In RESP2 a subscribed connection is crippled. Once it has any subscription it accepts only SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, SSUBSCRIBE, SUNSUBSCRIBE, PING, RESET, and QUIT. Anything else fails:

(error) ERR Can't execute 'get': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE /
PING / QUIT / RESET are allowed in this context

So a client library needs a second connection for normal commands, which is why every Redis client has a separate pub/sub connection or client object. RESET (6.2+) clears the state and returns the connection to normal.

Under RESP3 (HELLO 3) push messages travel on a separate protocol channel, so one connection can subscribe and run regular commands. That is the modern answer, and it is what newer clients use by default.

Sharded pub/sub, for Cluster

Regular pub/sub in Cluster mode broadcasts every message to every node, because any node might hold a subscriber. That does not scale with cluster size. Sharded pub/sub (7.0+) hashes the channel name to a slot and delivers only within that shard.

SSUBSCRIBE hu:notify:1042      # subscriber connects to the node owning that slot
SPUBLISH   hu:notify:1042 '{"type":"mention"}'
PUBSUB SHARDCHANNELS
PUBSUB SHARDNUMSUB hu:notify:1042
PUBLISHSPUBLISH
Cluster trafficBroadcast to every nodeOnly the shard owning the channel's slot
Where to publishAny nodeThe node owning that slot, or use a cluster-aware client
Where to subscribeAny nodeThe node owning that slot
PatternsPSUBSCRIBE worksNo pattern form exists
Scales with node countNoYes

The two namespaces are completely separate. SPUBLISH does not reach a SUBSCRIBEr and PUBLISH does not reach an SSUBSCRIBEr, even on the identical channel name. On a standalone server both work, so you can develop against SPUBLISH before you have a cluster.

Keyspace notifications

Redis can publish its own events, which is how you observe expiry, eviction, and writes without polling. It is off by default because it costs CPU on every matching command.

CONFIG SET notify-keyspace-events "KEA"    # everything
CONFIG GET notify-keyspace-events          # "AKE", normalized order
CONFIG SET notify-keyspace-events "Ex"     # expired events only, keyevent channel
CONFIG SET notify-keyspace-events ""       # off

The value is a flag string, and you must include at least one of K or E or nothing is published at all.

FlagEnables
K__keyspace@<db>__:<key> channels, payload is the event name
E__keyevent@<db>__:<event> channels, payload is the key name
gGeneric commands, DEL, EXPIRE, RENAME
$String commands
l s h z tList, set, hash, sorted set, stream commands
dModule key type events
xExpired events
eEvicted events (maxmemory pressure)
nNew key events (7.0+), no key name in the payload
mKey-miss events
AAlias for g$lshzxetd, everything except m and n
# Watch every event in db 0
redis-cli PSUBSCRIBE '__key*@0__:*'

# React to expiring sessions only
redis-cli PSUBSCRIBE '__keyevent@0__:expired'

Real output for SET nk v PX 200 followed by the key expiring, with KEA on:

pmessage  __key*@0__:*  __keyspace@0__:nk           set
pmessage  __key*@0__:*  __keyevent@0__:set          nk
pmessage  __key*@0__:*  __keyspace@0__:nk           expire
pmessage  __key*@0__:*  __keyevent@0__:expire       nk
pmessage  __key*@0__:*  __keyspace@0__:nk           expired
pmessage  __key*@0__:*  __keyevent@0__:expired      nk

Note the direction: the keyspace channel names the key and the payload is the event, and the keyevent channel names the event and the payload is the key. Pick whichever way you want to filter.

Caveats that make this unsuitable as an event source of record:

- Delivery is best effort over pub/sub. A subscriber that is down misses events with no trace, so never use expired as a job trigger for anything that matters. - The event carries the key name, never the value. By the time you see expired or del the value is already gone, so store what you need elsewhere. - expired fires when the key is actually removed, which is on access or on the sampled background cycle, so it can arrive noticeably after the TTL elapsed. - Notifications are per database and the channel name embeds the number, so a SELECT 1 writer publishes to __keyevent@1__:*.

When pub/sub is the wrong tool

RequirementUse instead
A subscriber may be offlineStream with a consumer group
Acknowledgement or retryStream, XACK plus XPENDING
Replay historyStream, XRANGE
Exactly one worker per messageList with BLMOVE, or a stream group
Durability across a restartStream, or an actual broker
BackpressureAnything else, see below

The failure mode people hit in production is the client output buffer. A slow subscriber accumulates undelivered messages in server memory, and once client-output-buffer-limit pubsub is exceeded Redis disconnects it, silently from the publisher's point of view.

CONFIG GET client-output-buffer-limit
# pubsub default: hard 32mb, soft 8mb over 60 seconds
CONFIG SET client-output-buffer-limit "pubsub 32mb 8mb 60"

Pub/Sub is genuinely the right answer for cache invalidation fan-out, live presence and typing indicators, config reloads across a fleet, and waking workers that will re-read state from a durable place anyway. In each of those, losing a message means a slightly stale read, not lost work. That is the test to apply.