Topic 5.5
Keyspace Notifications and Client-Side Caching
In one line
Keyspace notifications publish events when keys change or expire, useful for reacting to changes but fire-and-forget. Client-side caching (CLIENT TRACKING) lets apps keep hot keys in local memory while Redis sends invalidation messages when those keys change: a near-cache with server-assisted consistency.
Think of it like this
A newspaper subscription where the publisher calls you if an article you clipped gets corrected. You keep your clipping (local cache) and read it instantly, and you only throw it out when you get the call (invalidation).
Key ideas
- 01
Keyspace notifications: enable with
notify-keyspace-events(for exampleExfor expired events,KEAfor everything). Redis publishes on__keyspace@0__:<key>(event name as message) and__keyevent@0__:<event>(key name as message). They use Pub/Sub, so they're lost if nobody's listening, and in Cluster each node only publishes events for its own keys. - 02
Expired events fire when Redis actually deletes the key (lazily or in the active cycle), which can be later than the TTL moment. Don't use them as precise timers or as the only trigger for important work.
- 03
Client-side caching:
CLIENT TRACKING ONmakes Redis remember which keys this connection read; when any of them changes, Redis sends an invalidation (a RESP3 push, or on RESP2 via a redirected Pub/Sub connection on__redis__:invalidate). The client drops the local copy and fetches fresh data next time. - 04
Modes: default mode tracks exact keys per client (uses server memory, bounded by
tracking-table-max-keys);BCAST PREFIX user: PREFIX product:sends invalidations for any change under the prefixes without per-key memory on the server;OPTIN/OPTOUTlet the client choose which reads to track.NOLOOPskips invalidations for the client's own writes. - 05
What you gain: local reads in nanoseconds for hot keys, far fewer network round trips, and relief for hot-key shards. What you give up: memory in every app instance, a short window where a local copy may be stale (between the write and the invalidation arriving), and complexity when connections drop (flush the local cache on reconnect).
- 06
Client support has grown (for example Lettuce, Jedis, redis-py and node-redis have client-side caching support in recent versions, mostly requiring RESP3). Check your client version before relying on it.
Code & diagrams
127.0.0.1:6379> CONFIG SET notify-keyspace-events Ex
OK
127.0.0.1:6379> PSUBSCRIBE __keyevent@0__:expired
# In another terminal:
127.0.0.1:6379> SET temp:1 x PX 100
# The subscriber receives, when the key is actually removed:
1) "pmessage"
2) "__keyevent@0__:expired"
3) "__keyevent@0__:expired"
4) "temp:1"RESP3 connection: invalidations arrive as push messages on the same connection.
127.0.0.1:6379> HELLO 3
...
127.0.0.1:6379> CLIENT TRACKING ON
OK
127.0.0.1:6379> GET product:9
"{...}" # client caches this locally
# Another client runs: SET product:9 "{new}"
-> invalidate: ["product:9"] # push message; client drops its local copy
# Broadcast mode: no per-key memory on the server
127.0.0.1:6379> CLIENT TRACKING ON BCAST PREFIX product: PREFIX flags:
OKInterview problem
The problem
Hot feature flags read 200K times per second
Every request reads ~20 feature flags from a Redis hash. At 10K requests/sec per service × 20 services, the flags shard is overloaded. Flags change a few times a day and changes should apply within about a second. Design a solution.
You're given
- 200K+ flag reads/sec
- Changes rare
- Propagation within ~1 s
- Many app instances
The interviewer follows up
Why not use keyspace notifications to trigger the refresh?
When it breaks
Business logic that depends on expired-key notifications
What you see
Actions happen late (keys expire lazily) or not at all (the listener was disconnected, or events went to another cluster node).
Fix & prevent
Use a sorted-set scheduler polled by workers for timed actions; treat notifications as optional hints.
Client-side cache not flushed after a reconnect
What you see
Invalidations sent while disconnected are lost; the instance keeps serving stale values indefinitely.
Fix & prevent
Flush the local cache on every reconnect and put a TTL on local entries as a backstop.
Explain it without notes
Explain how CLIENT TRACKING keeps a local cache consistent.
Practice
Enable expired notifications in the lab and log every expired key for a minute. Then set 1,000 keys with 1 s TTL and compare the log timing with the TTLs.
Trade-offs
- ↔
Client-side caching massively reduces load and latency but spends app memory and accepts a tiny staleness window.
- ↔
Broadcast tracking saves server memory but sends more invalidations than necessary.
Done when you can
I can enable and consume keyspace notifications and know their limits.
I can design a near-cache with
CLIENT TRACKINGor TTL plus Pub/Sub.