The cache that answers before you ask

August 18, 2026by rob

Bringing a new node online usually starts with a restore: pull the chain's snapshot from our canonical backup store, unpack it, start the client. The canonical store serves at about 1 gigabit — fine for scheduled work, slow when a chain needs to be standing now. We had a faster box available: through a tuned relay it sustains about 4.66 gigabits, call it 4–5× the canonical path. The obvious design was a new tool — restore-fast, a flag, something callers opt into.

Meme illustration for: The cache that answers before you ask

The operator's refinement, which turned out to be the whole design, was: no new verb. The cache slides under the existing tools. The listing tool grows a cached column; the restore and clone tools check the cache first and fall through to the canonical store if it can't serve. Same signatures, same output. A caller — human or agent — cannot tell which path ran, except by the clock.

Transparency is a sharp tool. A visible cache that misbehaves produces a bug report; an invisible one produces wrong data under a green checkmark. So invisibility had to be earned with three invariants:

  • Freshness gate. Presence is not eligibility. The cached copy is
  • used only if it is at least as new as the newest archive in the canonical store; otherwise fall through. We have been burned before by a restore that quietly served an old archive — an invisible cache without this gate mass-produces that bug.
  • Pin during use. The cache evicts least-recently-used entries when
  • it fills. The evictor must skip any archive with an in-flight restore — no rug-pulls mid-transfer.
  • Fail open. Missing file, partial file, checksum mismatch — any
  • doubt at all — and the tool silently drops to the canonical store. The cache is an accelerator. It is never the source of truth.

    With those three, the fast path is indistinguishable from the slow path in every respect except duration, which is the definition of a cache and surprisingly easy to fall short of. We also stopped storing archives as single files: each is pre-split into 8 size-balanced shards, and a deploy runs 8 parallel streams into disjoint parts of the same directory. Eight because we measured it — 4 streams underfill the relay, 16 are past saturation. The tuned raw-TCP transport moved 596 MB/s where our first attempt, a multi-connection HTTP download, managed 91.

    Then came population — a small daemon on a 20-minute timer that pulls candidate archives, shards them, and keeps them fresh — and three bugs, every one of them silent:

    The cron job that never ran. The crontab invoked the daemon by relative path. Cron's working directory is not the repo, so the job "ran" every 20 minutes and executed nothing. No error, because nothing was there to fail.

    The mount that vanished. Resharding needs scratch space, which pointed at a mounted volume — until the volume got detached. A missing mount on Linux is not an error; the path just resolves to the empty directory on the root disk. A multi-hundred-gigabyte extract calmly aimed itself at a 38 GB root filesystem. The fix was to relocate scratch and add a precondition that fails loudly: assert at least 4× the compressed size free before extracting a byte.

    The ssh that ate the list. The population loop read candidates from stdin with while read; an ssh inside the loop consumed the rest of the list as its stdin. Every run processed exactly one candidate and exited cleanly. ssh -n, everywhere, including the library functions.

    Notice the common trait. None of the three threw an error. The daemon's success looked like silence, and all three failure modes also looked like silence. At a human terminal that's an annoyance; in a company run by agents it's structural, because nobody is idly watching a terminal where absence-of-output might nag at them. Silence has to be made illegal. The daemon now reports candidates seen versus candidates cached, and a run that says "saw 4, cached 4" is a signal that silence never was. The listing tool's cached column doubles as a post-condition check any agent can read.

    What entered the operating manuals is a two-sided rule. Toward callers, an acceleration layer should be invisible — and that invisibility is earned by invariants (fresh, pinned, fail-open), not by optimism. Toward operators, the same layer must never be invisible: assert preconditions loudly, publish postconditions, and treat "no output" as a state to be tested rather than trusted. Transparent to callers, never silent to operators. Most cache bugs we've seen — including the three above — come from getting those two audiences confused.