R1.04.3 · Client-side caching — dive 3
The SHA1 script-cache parallel.
Run a Lua script by its hash, not its source — and on a cache-miss reply, send the source once. The same cache-then-invalidate shape, in real EchoMQ code.
Grounding: echo_wire/script.ex (EchoMQ.Script.new/2, EVALSHA-first) · echo_store/coherence.ex (the worked @drop = Script.new) — shown as a parallel, not a door.
Run by hash, resend on miss
source · the cache-then-invalidate shapeLoading a script with SCRIPT LOAD registers its body in the server's script cache and returns
its SHA1. From then on a client runs it with EVALSHA — sending the 40-character hex hash, not
the script text. The body travels the wire once; every later run is a hash. The hit case is a pure win: less
data sent, no parse.
The miss case is the half that makes the parallel exact. If the server has dropped the script — a
restart, a SCRIPT FLUSH, an unseen replica — EVALSHA returns a
NOSCRIPT error. That error is the invalidation: the client's assumption that the server holds the
script is wrong, so the client falls back to EVAL, sending the full source, which re-registers the
hash. One miss, one resend, then hits again. A near cache drops a value on a broadcast push; a script cache
resends a body on a NOSCRIPT. The signal differs; the shape is the same.
- EVALSHA
- Run a server-cached script by its SHA1 hash; the body is not sent.
- NOSCRIPT
- The reply when the server does not hold that hash — the cache-miss signal.
- EVAL
- The fallback: send the full source, run it, and re-register the hash for next time.
- SHA1
- The content hash that names a script; the cache key on both sides.
In EchoMQ.Script
echo_wire/script.ex · verbatimEchoMQ runs its state transitions as Lua, so the same scripts run constantly — which is exactly the
case the SHA1 cache is for. The real type is EchoMQ.Script in
echo/apps/echo_wire/lib/echo_mq/script.ex. It is a tiny struct that precomputes the SHA1
at definition time — the cache key is derived once, not on every call — so the connector
can run EVALSHA-first with a load-on-NOSCRIPT fallback:
defmodule EchoMQ.Script do @moduledoc """ A server-side script with its SHA1 precomputed, so the connector can run EVALSHA-first with a load-on-NOSCRIPT fallback. ... """ defstruct [:name, :source, :sha] def new(name, source) when is_atom(name) and is_binary(source) do sha = :crypto.hash(:sha, source) |> Base.encode16(case: :lower) %__MODULE__{name: name, source: source, sha: sha} end end
The worked example sits in EchoStore itself: the version-safe L2 drop is a module attribute, defined once
with its SHA1 already computed, then dispatched by hash through the connector's eval:
# echo/apps/echo_store/lib/echo_store/coherence.ex — the cache key minted once @drop Script.new(:coherence_drop, """ local cur = redis.call('GET', KEYS[1]) if not cur then return 0 end ... """) def drop_l2(conn, table, id, version) do Connector.eval(conn, @drop, [Keyspace.key(table, id)], [version]) # EVALSHA-first end
Notes on ValkeyEVALSHA runs a script the server already
holds by hash; a NOSCRIPT reply is the only miss, and the client loads the source once on it. The
committed connector record shows the discipline at work: script_loads=1 — exactly one load per
script per connection —
valkey.io/topics/programmability.
The same pattern, mapped
near cache → script cacheSet the two side by side. Each role is one part of the near-cache pattern and its counterpart in
EchoMQ.Script. Pick a role to read both sides at once; the cache-then-invalidate shape is the same
down every row.
The EVALSHA-first protocol, in depth
The SHA1 cache is the client-side-caching idea applied to scripts — but the full story behind
EchoMQ.Script (the whole Lua bundle, the version fence, the protocol governance) is a course of
its own. This dive draws the parallel; the EchoMQ course teaches the protocol.
Hold a value locally; read it from memory. On a change, a broadcast push drops the copy, and the next read fetches once and re-caches.
Hold a script's precomputed SHA1; run it by hash with eval. On a NOSCRIPT reply
the assumption is dropped, and the connector resends the full source once — the real
Script.new/2.
Cache a derived thing near the work; take a signal that it is gone, and refill once. The near
cache and EchoMQ.Script are the same pattern with different cargo.
References
sources · relatedSources
- Valkey — Scripting with Lua —
EVALSHA, the script cache, and theNOSCRIPTreply on a miss. - Redis — EVALSHA — running a cached script by hash, and the
NOSCRIPTerror on a miss. - Redis — Client-side caching — the near-cache pattern the script cache mirrors, cache-then-invalidate.
Related in this course
- R1.04 · Client-side cachingthe module hub
- R1.04.2 · The invalidation pushthe near-cache half of the parallel
- R1 · Cachingthe chapter
- R0 · OverviewValkey under codemojex
- /echomq/cachethe EchoStore coherence Lua, loaded once, in depth