Chapter 16 · Pub/Sub, Keyspace Notifications, and Messaging Tradeoffs
Keyspace/Keyevent Notifications: Configuration, Event Types, Overhead, and Race Conditions
Configure only the keyspace/keyevent notifications needed for an AtlasMart fixture, observe their lossy Pub/Sub delivery, and prove why every signal must be reconciled with current state.
Learning outcomes
AtlasMart wants to refresh an in-process cache whenever a Redis key changes. Keyspace notifications can emit a signal automatically, but they are disabled by default, consume CPU when enabled, and use Pub/Sub delivery. This lesson configures only a narrow event set and proves that the notification is a hint about an event—not an immutable copy of business state.
Distinguish keyspace channels from keyevent channels and decode the notify-keyspace-events configuration flags.
Save and restore the original notification configuration instead of leaving a global lab change behind.
Observe SET/EXPIRE/expired events with bounded fixtures and explain expiration timing without promising an exact deadline.
Demonstrate the race between receiving a notification and re-reading the key after a later write.
Estimate notification overhead from event rate/selectivity and avoid enabling broad event classes without a measured need.
All Chapter 16 mandatory labs reuse the disposable Chapter 01
environment: Redis Open Source 8.10.1 from pinned
Docker image redis:8.10.1, container
atlasmart-redis-ch01, standalone topology,
endpoint 127.0.0.1:6379, TLS disabled only
because traffic is loopback-local, default ACL user disabled,
named academy-admin and
atlasmart-app users, logical database 0, AOF with
appendfsync everysec plus RDB snapshots,
persistent /data, and no explicit
maxmemory/eviction policy. Python examples target
redis-py 8.1.0. The Chapter 01 application ACL
intentionally does not assume Pub/Sub command permissions, so
Pub/Sub/configuration demonstrations use
academy-admin until Lesson 5 creates a temporary
least-privilege Chapter 16 user. Fixtures stay under
atlasmart:ch16:.
1. Two channel families expose the same underlying event differently
A keyspace notification uses channel
__keyspace@<db>__:<key> and puts the
event name in the payload. A
keyevent notification uses channel
__keyevent@<db>__:<event> and puts the
key name in the payload. Both are ordinary Pub/Sub pushes: if
the observer is disconnected, the signal is lost.
| Mode | Channel answers | Payload answers |
|---|---|---|
| Keyspace (K) | Which key changed? | What event happened? |
| Keyevent (E) | What event happened? | Which key changed? |
2. Notifications are disabled by default for a reason
notify-keyspace-events is empty by default in a
normal Redis configuration because generating and publishing
event signals uses CPU. The configuration string combines
delivery family (K/E) with event
classes such as generic commands (g), String
commands ($), expired events (x), and
others. At least K or E is required or
nothing is emitted.
CONFIG GET notify-keyspace-events# Record the exact original value. The lab restores it in a finally block.
The common KEA example enables a broad event set.
This lab uses only the event classes needed for
String/generic/expiry evidence and restores the prior
configuration afterward.
3. Reproducible listener that restores configuration
Using an administrative client is deliberate because
CONFIG SET is operationally privileged. The
listener enables KEg$x: keyspace plus keyevent
channels, generic events, String events, and expired events. The
dollar sign is a Redis configuration flag, not shell
interpolation because this example is Python.
import time, redisadmin = redis.Redis(host="127.0.0.1", port=6379, username="academy-admin", password="AtlasMart-Admin-Lab-Only-2026", decode_responses=True)old = admin.config_get("notify-keyspace-events").get("notify-keyspace-events", "")ps = admin.pubsub()try: admin.config_set("notify-keyspace-events", "KEg$x") ps.psubscribe("__keyspace@0__:atlasmart:ch16:l3:*", "__keyevent@0__:*" ) time.sleep(0.05) admin.set("atlasmart:ch16:l3:item", "v1") admin.expire("atlasmart:ch16:l3:item", 2) end = time.monotonic() + 4 seen = [] while time.monotonic() < end and len(seen) < 4: m = ps.get_message(ignore_subscribe_messages=True, timeout=0.1) if m: seen.append((m.get("channel"), m.get("data"))) print("events", seen) print("current_value", admin.get("atlasmart:ch16:l3:item"))finally: ps.close() admin.config_set("notify-keyspace-events", old) admin.unlink("atlasmart:ch16:l3:item") print("restored", admin.config_get("notify-keyspace-events"))# Expired-event timing is asynchronous; do not assert an exact millisecond deadline.
4. Event names are not snapshots
A notification tells you that Redis emitted an event such as
set, del, expire, or
expired. It does not contain a versioned snapshot
of the value. By the time a listener handles the push, later
writes may already have changed the key. Correct consumers
therefore treat the event as “something changed; re-read if
needed.”
SET atlasmart:ch16:l3:race v1SET atlasmart:ch16:l3:race v2GET atlasmart:ch16:l3:race# A delayed listener may process the first set notification after v2 is already authoritative.
5. Expire versus expired are different events
Setting a time-to-live can emit an expire-class
signal about configuring expiration. The later
expired event is generated when Redis actually
removes the expired key through its expiration mechanisms. Redis
expiration is not an exact scheduler; the event should not be
used as a hard real-time timer or compliance deadline.
| Signal | Meaning | Do not infer |
|---|---|---|
| expire | expiration metadata was applied/changed | the key is already gone |
| expired | Redis removed an expired key | exact wall-clock deletion at TTL=0 |
| del | a deletion event occurred | why the business entity was deleted without re-reading context |
6. The configuration string is an event-volume decision
Every enabled class can generate publications. Enabling broad keyspace events on a high-write server increases CPU work and subscriber traffic. Estimate event rate from the actual command mix and key scope, then measure CPU, network, subscriber callback time, and useful-signal ratio. The notification configuration is global to the Redis server, not scoped to one AtlasMart prefix.
| Question before enabling | Why it matters |
|---|---|
| Which event classes are required? | Avoid publishing irrelevant traffic |
| How many writes/expirations per second? | Estimate signal volume and CPU/network cost |
| How many observers? | Fan-out multiplies downstream work |
| What if observer is offline? | Signals are lost; reconciliation must exist |
| Can the observer re-read authoritative state? | Needed for race-safe correctness |
7. Keyspace notifications share Pub/Sub ACL and delivery boundaries
Observers need Pub/Sub permission for the reserved
__keyspace@...__ /
__keyevent@...__ channels, while the client
performing writes needs key permissions separately. Channel
Access Control List (ACL) patterns and key ACL patterns are
different security controls. Do not grant
&* merely to make an observer work.
For PSUBSCRIBE, Redis ACL checks require the
subscription pattern itself to be permitted appropriately.
Design a narrowly matched observer user and verify it with ACL
DRYRUN/real subscription in an isolated lab.
8. Version-sensitive extension: subkey notifications
Current Redis documentation also exposes subkey notifications for selected hash-field operations. That is a separate feature surface with its own channel families/configuration and evolving command coverage. Chapter 16 does not make it mandatory for the core keyspace lesson; check the exact target Redis patch/client support before adopting it and keep fallback reconciliation at whole-key/application-state level.
9. Failure/misuse: treating the notification as business truth
Suppose AtlasMart receives
__keyevent@0__:set → atlasmart:order:1001 and sends
“order paid” without re-reading. A later write may already have
changed the order to refunded, or the event may refer to a
different field entirely. The repair is to re-read authoritative
state and, for durable workflows, process a versioned
Stream/broker event instead of inferring business meaning from a
generic Redis mutation signal.
Keyspace/keyevent notifications are excellent cache-invalidation/wake-up hints. They are weak business-domain events because they describe Redis mutations, not an immutable application event contract.
10. Verification checklist
-
The original
notify-keyspace-eventsvalue was captured and restored. - Only bounded Chapter 16 keys were changed.
- Both keyspace and keyevent channel shapes were explained.
- The listener re-read current state instead of trusting the push as a snapshot.
- Expiration timing was treated as asynchronous rather than exact.
UNLINK atlasmart:ch16:l3:race
11. Production judgment
Enable notifications only for a measured need such as cache invalidation, live operational hints, or lightweight observability where loss is acceptable. Avoid broad classes on high-write servers without load testing. Configuration changes should be version-controlled and rollout-safe; in managed Redis, CONFIG permissions/options may differ. Replication/failover do not turn Pub/Sub notifications into a durable log, so every correctness-sensitive observer needs reconciliation.
Check your understanding
- Why are notifications disabled by default?
- What is the difference between keyspace and keyevent channels?
- Why re-read after receiving a notification?
- Does expired mean exact TTL deadline execution?
- Why restore notify-keyspace-events after the lab?
Review the answers
Generating event notifications consumes CPU and can create significant downstream traffic.
Keyspace channels identify the key and payload the event; keyevent channels identify the event and payload the key.
The push is not a value snapshot and later writes may already have changed state.
No. Redis expiration is not an exact scheduler.
It is a server-global configuration that could add overhead/noise for unrelated workloads.
12. Summary and next step
Keyspace notifications automate signal generation but inherit Pub/Sub loss and race semantics. Lesson 4 now compares that ephemeral model with Redis Streams and external brokers so replay, consumer state, ordering, and durability are chosen explicitly rather than by habit.
Authoritative references
- Redis Open Source 8.10 release notes
- Redis Pub/Sub
- Redis Pub/Sub use case
- Redis Pub/Sub with redis-py
- PUBLISH
- SUBSCRIBE
- PSUBSCRIBE
- UNSUBSCRIBE
- PUNSUBSCRIBE
- SSUBSCRIBE
- SPUBLISH
- SUNSUBSCRIBE
- PUBSUB CHANNELS
- PUBSUB NUMSUB
- PUBSUB NUMPAT
- PUBSUB SHARDCHANNELS
- PUBSUB SHARDNUMSUB
- Redis keyspace notifications
- Redis subkey notifications
- CONFIG GET
- CONFIG SET
- CLIENT LIST
- CLIENT SETNAME
- Redis client output-buffer limits
- Redis Streams
- Redis streaming use case
- XADD
- XREADGROUP
- XPENDING
- XACK
- Redis ACLs
- ACL SETUSER
- ACL DRYRUN
- Redis Cluster specification
- Redis replication
- Redis Sentinel
- Redis persistence
- redis-py documentation
- redis-py 8.1.0 on PyPI
- Redis licenses