Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Cache Coherence

This concerns only the shared-store / no-consensus topology — N stateless processes over one shared KvStore, each fronting it with a local CachedKv LRU. The Raft topology needs none of it (replication keeps every node’s applied state current; RaftKv has no LRU). A single process doesn’t either.

The goal: a process picks up another process’s control-plane write promptly and cheaply, scaling to thousands of sites — without TTL desync and without flushing the world on every write.

Why not the obvious options

  • Per-entry TTL — every entry goes stale on its own clock; you tune a guess and live with desync.
  • Flush-all on any write — one site’s edit flushes every process’s whole LRU → all frontends re-fetch their working set → a thundering herd on every write. Cost scales with cache_size × write_rate. Kept only as a rare backstop.

Targeted invalidation via a changelog

Invalidate only the changed keys (pop site X’s entries; leave the others hot). Cost is O(write rate), independent of site count; O(1) per change.

On a control-plane write, one entry _inval/{millis}-{writer}-{n} listing the changed keys is appended to the shared store. Each process polls for entries after its cursor, pops those keys from its LRU, and advances the cursor (its own entries are skipped). Old entries are trimmed; a rare full flush is the gap backstop. The feed is just KV data, so it works over Cloudflare KV or shared SlateDB alike. Enable with --shared-cache-coherence.

For real-time (poll-free) delivery, a pusher (a Cloudflare Durable Object / Queue, Redis, or ops) can POST /api/cache/invalidate {keys:[…]} directly.

Minimizing the surface: content-addressed config

The fewer mutable keys, the smaller the problem. SiteConfig is content-addressed: an immutable siteconfig/<hash> body (caches forever, dedups across sites) plus a tiny mutable site/<site> pointer. Only the pointer changes on an edit, so the feed carries pointers, not config bodies — and the bodies never need invalidation at all. (This also makes config edits atomic pointer flips, like deploy activation.)

What is never cached

Coordination state — rate-limit windows (ratelimit/<site>/<ip>) and messaging claim/lease state (mqp/…) — is read through the uncached backend; caching it would yield stale leases / wrong counts in shared mode.