Level 2 · 20 min
Pub/Sub
Redis Pub/Sub is a fire-and-forget messaging system built into Redis. It enables real-time event distribution but has important limitations that make it unsuitable for reliable messaging.
Pub/Sub Channels
SUBSCRIBE puts the client connection into subscriber mode — only SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PING, RESET, and QUIT are permitted on that connection afterward; all other commands return an error. A dedicated connection is required. PSUBSCRIBE uses glob matching: *, ?, [ae], [^x]. PSUBSCRIBE news.[st]* matches news.sports and news.tech. Pattern matching runs O(N) over all active patterns per PUBLISH call — avoid thousands of concurrently active PSUBSCRIBE patterns. Message frame format differs: plain SUBSCRIBE delivers 3-element arrays [message, channel, payload]; PSUBSCRIBE delivers 4-element arrays [pmessage, pattern, channel, payload]. PUBLISH returns the integer count of subscribers that received the message — a return value of 0 means the message was dropped and no subscriber was active; applications must check this and handle the zero case explicitly.
Pub/Sub Limitations
A production incident at a payments company: a notification service used Pub/Sub to fan out transaction events at 8,000 messages/second. During a 20-pod rolling deployment, each pod restart caused a ~200ms subscription gap. Across 20 restarts, approximately 16,000 messages were silently dropped. No monitoring caught it because PUBLISH returned 0 during gaps but the application never checked that return value. Streams with XADD and consumer groups solved this: each consumer group tracked its own read cursor, and reconnecting consumers replayed from the last ACKed ID. Slow-subscriber risk: if a subscriber's client-output-buffer-limit pubsub threshold is reached (default 32MB hard, 8MB soft for 60 seconds), Redis forcibly disconnects the client without sending a notification — the publisher continues publishing, the consumer silently vanishes. Production insight from Redis in Action: "If a client that had subscribed to channels but didn't read sent messages fast enough could cause Redis itself to keep a large outgoing buffer. If this outgoing buffer grew too large, it could cause Redis to slow down drastically or crash, could cause the operating system to kill Redis, and could even cause the operating system itself to become unusable." — Josiah Carlson, Redis in Action, ch. 3. Modern Redis mitigates this with client-output-buffer-limit pubsub, but the fundamental fire-and-forget guarantee remains: "It's for these two reasons [reliability and buffer growth] that we write two different methods to handle reliable message delivery... which works in the face of network disconnections, and which won't cause Redis memory to grow unless you want it to" — Carlson, ch. 3. The practical takeaway: treat Pub/Sub as a real-time notification hint, not a delivery guarantee — always pair it with a durable fallback (Streams XADD/XREAD or a persistent queue) for any message that must not be lost.
Keyspace Notifications
Keyspace notifications publish to channels __keyevent@{db}__:{event} and __keyspace@{db}__:{key}. notify-keyspace-events config flags: K (keyspace events), E (keyevent events), g (generic: DEL, EXPIRE, RENAME), x (expired), e (evicted), s (set commands), z (sorted set). KEA enables all events — avoid in high-write production since it doubles effective publish throughput. For session expiry detection, Kex (keyevent + expired only) minimizes overhead. Critical behavior: expired keyspace notifications fire when Redis lazily evicts a key (on access or active expiry cycle), not precisely at TTL=0. Under memory pressure or with hz set low, expiry can lag by seconds. Set hz=20 (double the default 10) to increase active expiry sweep frequency, at the cost of ~1-2% additional base CPU.
Code example
# Publisher (in one connection)
PUBLISH order:updates {"orderId": "123", "status": "shipped"}
# Subscriber (in another connection)
SUBSCRIBE order:updates
# Receives: [message, order:updates, {"orderId": "123", "status": "shipped"}]
# Pattern subscribe: all events in orders.*
PSUBSCRIBE order:*
# Keyspace notifications: detect key expiry
# redis.conf: notify-keyspace-events KEA
SUBSCRIBE __keyevent@0__:expired
# When session:user123 expires, you get notified