R1.04.1 · Client-side caching — dive 1
CLIENT TRACKING.
The server keeps a record of which keys each connection holds, so it can warn that connection the moment one changes.
Grounding: real CLIENT TRACKING · echo_store/table.ex (the RESP3
subscription) · echo_store/coherence.ex (the channel).
| conn 7 holds | since |
|---|
The server-side record
source · CLIENT TRACKINGTurn tracking on for a connection with CLIENT TRACKING ON. From then on, every key the
connection reads is noted by the server in a tracking table: the key, and the connections holding it. The table
is the whole mechanism. When a key is modified — by this connection or any other — the server looks
up who holds it and sends each of them an invalidation. The next dive covers that push; this one is about how
the record is built.
The default record is exact: the server remembers the precise keys a connection read, so an invalidation arrives only for keys the connection actually holds. Exactness has a cost — the server stores a table proportional to the number of distinct keys tracked across all connections. That is the memory the server spends to keep every near cache honest.
- tracking table
- The server-side record of which keys each connection has read; the basis for invalidation.
- tracked key
- A key a connection holds locally and the server has noted; a change to it triggers a push.
- RESP3
- The protocol revision whose push frames carry the invalidation message on the same connection.
- redirect
- An option to route invalidations to a second connection, for clients that read on RESP2.
Default vs broadcast
source · BCASTThere are two ways to keep the record. Default mode tracks the exact keys each connection
read — precise, but the server holds a per-key table. Broadcast mode
(CLIENT TRACKING ON BCAST PREFIX set:) tracks prefixes instead: the server keeps no
per-key table and instead notifies every broadcast subscriber whenever any key under a registered prefix
changes. Cheaper for the server, but a connection may receive invalidations for keys it never read.
The choice is a trade between server memory and notification precision. Compare them below: pick a mode and read which one each side pays for.
Default mode spends server memory to be precise. Broadcast spends precision to save memory. The workload — how many distinct hot keys, how wide the prefix — decides which is cheaper.
The near cache in EchoStore
echo_store/table.ex (init)Picture codemojex's emoji-set read path with a near cache in front of it. The scoring consumer
reads an emoji set often and the set never changes for the round's life. With a near cache on, the table holds the
set in local L1 ETS and lets the server carry the change notification; later reads are memory lookups.
The first read still falls through to Valkey for the value, and the table populates both the L2 row
(ecc:{cm_emojisets}:<id>) and the local copy. Every later read of the unchanged set
stays in the process.
EchoStore rides the broadcast shape over plain pub/sub. The table opens a RESP3 connection at start and
subscribes to its coherence channel — the real init path:
# echo/apps/echo_store/lib/echo_store/table.ex (init) {:ok, sub} = Connector.start_link( Keyword.get(opts, :connector, []) |> Keyword.merge(protocol: 3, push_to: self(), heartbeat_ms: 0) ) :ok = Connector.subscribe(sub, Coherence.channel(table_str)) # ecc:{<table>}:coh
The channel name derives from the table name and nothing else —
Coherence.channel("cm_emojisets") is ecc:{cm_emojisets}:coh. A writer that changes a
set publishes on that channel; the next dive follows the push.
One hash-tagged key, one cluster slot
The L2 row a near cache falls through to is the cache's own key form —
ecc:{<table>}:<id> — a fresh prefix beside emq:, with the table
name hash-tagged so every key of one cache lands in one cluster slot. The first read fills both layers under a
TTL with a single atomic command:
# the L2 fill, value and TTL set atomically (id is a 14-byte branded id)
SET ecc:{cm_emojisets}:<id> <version+value> PX 300000
Notes on ValkeyThe broadcast subscription rides plain pub/sub, which is at-most-once — a message is delivered once if at all. A lost notification costs one TTL of staleness, the floor under the loss, which is why a risk surface rides the durable job lane instead — valkey.io/topics/pubsub.
The server records which keys a connection holds, so it can warn that connection when one changes. Default mode is exact and table-backed; broadcast trades a table for prefix subscriptions.
The table reads a hot emoji-set key into L1 ETS and keeps a local copy. A RESP3 subscription on
ecc:{<table>}:coh carries the change; the next dive applies it newer-wins.
Tracking is a server-kept record of who holds what. It is the half of the pattern that makes a near cache safe to trust.
References
sources · relatedSources
- Valkey — Client-side caching — server-assisted tracking, default and broadcast modes, and the invalidation message.
- Valkey — Pub/Sub — the channel the broadcast subscription rides; at-most-once delivery.
- Redis — CLIENT TRACKING — turning tracking on, the default and
BCASTmodes, andREDIRECT.
Related in this course
- R1.04 · Client-side cachingthe module hub
- R1.04.2 · The invalidation pushthe next dive
- R1 · Cachingthe chapter
- /echomq/cacheEchoStore :tracking over RESP3 CLIENT TRACKING, in depth