jonnify·redis /redis-patterns/caching/workshop

R1.07 · Caching — the chapter workshop

Cache the emoji set.

The chapter capstone: take the six patterns and put EchoStore in front of one real consumer read — codemojex’s emoji set, the reference data Codemojex.Guesses.submit/3 checks every guess against.

Grounding: echo/apps/echo_store (EchoStore.Table, Coherence) · echo/apps/codemojex (Codemojex.Guesses, Codemojex.Cache) · bcs.4.md — every figure verbatim from a committed record.

The catalog read path, in four layers. Select a layer to read which EchoStore surface it adds and the property it secures.
1 · cache-asideEchoStore.Table.fetch/3 2 · keep it consistentCoherence.broadcast / enqueue · newer? 3 · hardensingle-flight flights · jittered TTL 4 · measureEchoStore.Table.stats/1 counters L0 · Valkey — the engine under all four
readout · Select a layer. Cache-aside is the base; the three layers above it keep it consistent, absorb a herd, and report the hit rate — each one correct on its own.

Static verdicts — 1 cache → fetch/3 (:hit | :l2 | :fill) · 2 consistent → broadcast/enqueue + newer? · 3 harden → single-flight + jittered TTL · 4 measure → stats/1 counters.

§1

The consumer, and the read path in layers

codemojex/game.ex · echo_store/table.ex

codemojex validates every guess against the round’s emoji set. Codemojex.Guesses.submit/3 turns an untrusted guess into a branded JOB on the player’s lane or one closed error, and one of the first checks is the keyboard: it calls Codemojex.Cache.fetch_set(set_id) and runs EmojiSet.valid_guess?/2, answering {:error, :bad_guess} for a code the round’s keyboard does not expose. That emoji set lookup is reference data — read on every guess, immutable for the round’s life — which is exactly the read-heavy, write-light shape cache-aside is built for. Fronting it with EchoStore.Table is the workshop.

The read path stacks four layers, each adding one move taught earlier in the chapter, and each leaving the read correct on its own. The base layer is a cache-aside read: EchoStore.Table.fetch/3 checks L1 ETS, then on a miss runs a single-flight fill — GET ecc:{cm_emojisets}:<id>, the loader, then SET … PX — and returns {:ok, value, source} with source :hit | :l2 | :fill. The next layer keeps the cache consistent: a room’s emoji set changing runs Coherence.broadcast/4 on the fast lane or Coherence.enqueue/5 on the at-least-once job lane, and Coherence.newer?/2 resolves a late message newer-wins.

The third layer hardens the set every guess touches — EchoStore does this by construction: the single-flight flights map coalesces a herd onto one fill, and a jittered TTL (ttl ± ttl·jitter) keeps a cohort from expiring together. The fourth layer measures: EchoStore.Table.stats/1 returns the counter snapshot — hits, misses, fills, l2_hits, coalesced, swept, full_skips, sweeps, coh_applied, coh_stale plus live size — so the hit rate is a number, not a guess. Nothing here is new: each layer is a pattern from R1.01–R1.06, applied to one real surface. The work is the assembly.

The emoji-set read path is cache-aside at the base, with three layers stacked on top: keep it consistent on a change, let the single-flight and jitter absorb a herd, and read the hit rate off the counters. Each layer is one move from earlier in the chapter.

§2

The three stages

cache → consistent → harden & measure

The workshop is three dives. Each takes one layer and builds it on the real consumer surface, ending with the read path one step closer to production. Take them in order: the cache must exist before it can be kept consistent, and it must be consistent before its hit rate means anything.

§3

Walk the build

echo_store/table.ex · coherence.ex

Pick a stage to see which EchoStore surface it adds to the read path and what the cache can do once it lands. The surface grows as the stages stack; the readout names the new calls and the property the layer secures.

Build the read path. Pick a stage; the readout names the surface so far and what it gains.
the assembled read path

Cache-aside at the base, coherence on a change, the single-flight and jitter under a herd, and counters reporting the hit rate — four moves layered into one read path, each correct on its own.

its EchoStore application

EchoStore.Table in front of codemojex’s emoji set: fetch fills on a miss, broadcast/enqueue keep it consistent, the jittered TTL and single-flight absorb a herd, and stats reports the hit rate.

Three stages, four layers, one read path. The workshop turns the chapter’s patterns into a real consumer’s catalog cache — cached, consistent, hardened, and measured.

the application · the read path on a measured floor

EchoStore against live Valkey

The figures the workshop reproduces are committed, from the EchoStore gate against the live engine — the L1 hit cost and the lane prices, figure by figure.

bcs.4.md · the EchoStore gate, against live Valkeyfrozen
L1 ETS hit                    1311621 reads/s   ← 762 ns each
L2 GET                        31 us          ← the L1 hit is 40 times cheaper
coherence · broadcast lane     72 us          ← fire-and-forget, at-most-once
coherence · job lane           148 us         ← at-least-once: 2.1 times the latency

Notes on ValkeyEvery L2 fill is SET … PX — value and expiry in one atomic command — so the second layer expires itself even if every node forgets; a separate EXPIRE would leave an immortal key if the process died between the two — valkey.io/commands/set.

§4

References

sources · related

Sources

Related in this course