R1.04 · Caching — module hub
Server-assisted client-side caching.
Eliminate network round-trips for frequently accessed keys by caching values in application memory, with the engine automatically sending invalidation messages when data changes.
Grounding: echo_cache/coherence.ex · echo_cache/table.ex · echo_wire/script.ex · content/bcs4.2.md — every figure verbatim from a committed record.
The architecture
source · echo_cache/table.exClient-side caching creates a multi-tier cache. L1 (local memory) is nanosecond access, held by the application; L2 (Valkey) is microsecond to millisecond access; L3 (the database) is millisecond access. The key innovation is that the server records which keys each client holds and sends an invalidation message when those keys change.
The payoff is the round-trip a cache-aside hit still pays. With cache-aside the data is in Valkey, one
network hop away; a hit is fast but still a request and a response across a socket. For a key read thousands of
times a second and changed rarely — an instrument record, a feature flag, a config blob — that hop
is pure overhead. A near cache removes it: a repeated read of an unchanged key is a memory lookup with no
network at all, and the value stays correct because the server pushes a message the moment it goes stale. This
is exactly the shape of EchoCache (echo/apps/echo_cache): an L1 ETS table in the
caller's process over an L2 Valkey row, with coherence carried by a server-side push.
A near-cache hit costs a memory read and zero network. The only cost is correctness — and the server pays that with an invalidation push.
Tracking modes
source · CLIENT TRACKINGDefault tracking. The server remembers which keys each client has requested. After
CLIENT TRACKING ON, every GET causes the server to record that this client accessed
that key; if another client modifies the key, an invalidation message is pushed to the original client. The
trade-off is that the server must maintain a table mapping keys to client IDs, consuming memory proportional to
the number of tracked keys.
Broadcasting mode. Instead of recording individual key accesses, clients subscribe to key
prefixes; the server broadcasts invalidation messages to all subscribers when any matching key changes. A
client running CLIENT TRACKING ON BCAST PREFIX user: PREFIX session: receives invalidations for
any key starting with user: or session:. The trade-off reverses: a client may receive
invalidations for keys it does not have cached locally, but server memory stays minimal because it tracks
prefix subscriptions, not individual accesses. EchoCache rides the broadcast shape over plain pub/sub: a table
subscribes to one channel, ecc:{<table>}:coh, and the writer publishes a message naming the
changed key.
How invalidation works. When tracking is enabled and a key changes, four steps follow. The
server checks which clients hold the key; it sends an invalidation message naming the key; the client removes
the key from its local cache; and the next access fetches fresh data. This keeps local caches synchronized
without polling. The message names the key and carries no value — it reports only that the held copy is
no longer trustworthy. In EchoCache the message carries one more thing than a bare CLIENT TRACKING
invalidation does — the writer's mint-time version — so a late stale message can never erase a
newer row.
CLIENT TRACKING ON # enable tracking for the connection CLIENT TRACKING ON BCAST PREFIX user: # broadcast mode with a prefix CLIENT TRACKING ON REDIRECT 123 # redirect invalidations to another connection CLIENT TRACKING ON NOLOOP # do not invalidate the writer's own copy CLIENT TRACKING OFF # disable tracking
Default tracking is exact and table-backed; broadcast trades a per-key table for prefix subscriptions — cheaper memory, coarser notification. The first dive measures that trade.
When to use it
source · considerationsThis pattern benefits ultra-high-throughput applications where network RTT matters, frequently accessed "hot" keys, read-heavy workloads with relatively stable data, and applications already holding local caches that need invalidation.
Three considerations bound it. Memory management — the application must cap its local cache size, evicting when memory is constrained. Complexity — handling invalidation messages requires dedicated connection management or async processing. Not for volatile data — if data changes constantly, invalidation traffic may exceed the benefit of caching. RESP3 carries push notifications natively, making invalidation handling cleaner; EchoCache requires it for the broadcast lane — the table opens a RESP3 connection precisely so a push and a reply can share one wire.
Notes on ValkeyServer-assisted client-side caching pushes an unversioned, orderless invalidation — it names a key and nothing more. That is the gap EchoCache's mint-time version closes: its 29-byte message carries the version, so newer-wins resolves a late stale message by comparison — valkey.io/topics/client-side-caching.
The broadcast lane in EchoCache
echo_cache/coherence.ex · bcs4.2The near cache the Exchange Platform would put in front of its instrument catalog is an EchoCache table: an
L1 ETS row in the caller's process over an L2 Valkey row keyed ecc:{instruments}:<id>.
Coherence is carried by a server push, not by polling. On start the table opens a RESP3 connection and
subscribes to its coherence channel; when a writer changes a row, it publishes the change on that channel, and
every subscribing table applies it newer-wins in its owner.
# echo/apps/echo_cache/lib/echo_cache/coherence.ex
def channel(table), do: "ecc:{" <> table <> "}:coh"
def payload(<<_::binary-14>> = id, <<_::binary-14>> = version), do: id <> ":" <> version
def broadcast(conn, table, id, version) do
Connector.command(conn, ["PUBLISH", channel(table), payload(id, version)])
end
Two lanes, one 29-byte message
The coherence message is the same twenty-nine bytes on either lane — a cached name, a colon, the writer's mint-time version. The broadcast lane is fast and lossy; the durable job lane survives a crash. The committed record prices both:
coherence message twenty-nine bytes ← name + ":" + mint-time version broadcast lane (PUBLISH) 72 us ← fire-and-forget, at-most-once job lane (EchoMQ) 148 us ← at-least-once, crash-surviving the guarantee costs 2.1 times the latency
The application caches a value locally and reads it from memory. The server tracks the key; on a change it pushes an invalidation, and the local copy is dropped.
An L1 ETS row over an L2 Valkey row; a RESP3 subscription on ecc:{<table>}:coh.
Coherence.broadcast/4 PUBLISHes the 29-byte message; the table drops L1 newer-wins.
Cache the hot and stable; leave the volatile in Valkey. The benefit is the round-trip removed; the cost is the memory the near cache holds and the invalidation traffic it must process.
The three dives
track → invalidate → the same shapeThe module takes the pattern in three steps, in the arc track → invalidate → the same shape
elsewhere: how the server records what each client holds, how it notifies a holder when a key changes, and
a real EchoMQ script cache that follows the identical cache-then-invalidate idea. Read them in order —
CLIENT TRACKING starts.
CLIENT TRACKING
The server-assisted near cache: tracking in default and broadcast mode, and the real RESP3 subscription EchoCache opens for the broadcast shape.
The invalidation push
When a tracked key changes, the server sends a RESP3 message naming the key, and the holder drops
its copy — the real Coherence.broadcast/4 and the 29-byte message.
The SHA1 script-cache parallel
EVALSHA with a precomputed SHA1 and a NOSCRIPT fallback — the same
cache-then-invalidate shape, in EchoMQ's real EchoMQ.Script.new/2.
The third dive draws a parallel only. The script cache uses the same
cache-then-invalidate idea as CLIENT TRACKING; the queue protocol itself — the full Lua bundle
and the version fence — belongs to the dedicated EchoMQ course, which this module links forward to rather
than teaches.
References
sources · relatedSources
- Valkey — Client-side caching — server-assisted tracking and its invalidation messages; the unversioned, orderless gap EchoCache's mint-time version closes.
- Valkey — Pub/Sub — at-most-once delivery, the broadcast lane's contract taken whole.
- Redis — Client-side caching — the server-assisted near cache and invalidation via
CLIENT TRACKING. - Redis — CLIENT TRACKING — default and broadcast modes,
REDIRECT,NOLOOP, andOPTIN/OPTOUT.
Related in this course
- R1.04.1 · CLIENT TRACKINGthe near cache
- R1.04.2 · The invalidation pushthe change notification
- R1.04.3 · The SHA1 script-cache parallelthe same shape, applied
- R1 · Cachingthe chapter
- R0 · OverviewValkey under the Exchange Platform
- /echomqthe EVALSHA/script-loading protocol in depth