CONCEPT Cited by 1 source
Invalidation-based cache¶
Definition¶
An invalidation-based cache keeps cached values until notified they might be stale, at which point the cached entry is evicted and (on next read) re-fetched from source. The cache never tries to apply the change to the stored value — it simply discards and refetches.
Contrast mutation-based cache (a.k.a. write-through with in-place update): the cache receives the change itself and mutates the cached entry in place to reflect it, avoiding the re-fetch.
Axis: where does the freshness cost live?¶
| Axis | Mutation-based | Invalidation-based |
|---|---|---|
| On-change work | Apply diff to every copy of the affected value | Mark/evict; re-fetch lazily on next read |
| Message payload | Full change content | Just "this key is stale" (small) |
| Ordering | Must preserve change order | Invalidations are idempotent / unordered |
| Re-query load on source | None per change | One per affected query, on next read |
| Coupling to source schema | Loose | Tight (must know which queries a row change affects) |
| Fit for | Source is slow / capacity-starved; changes are dense | Source can handle some re-query load; changes are sparse on active data |
Figma LiveGraph's explicit switch¶
LiveGraph's 100x rebuild flipped from mutation-based (old, pre-2024) to invalidation-based (new). The post names the reasoning explicitly (Source: sources/2026-04-21-figma-keeping-it-100x-with-real-time-data-at-scale):
- Old rationale (mutation-based): the single primary Postgres was "extremely sensitive to any spikes. Issuing too many queries at once could topple the database, so we used a mutation-based cache to deliver new results without re-queries. This was helpful at the time, but with database scaling, capacity is not nearly as precious."
- New rationale (invalidation-based): most LiveGraph traffic is initial reads, not live updates on active queries. So invalidations on live-subscribed queries are infrequent, and the re-query load they trigger is bounded. Measurement confirmed it before ship.
This is a canonical pattern: the right cache-freshness strategy depends on the relative cost of re-query vs of in-place diff application, and that cost ratio can flip as the source scales out.
Second-order consequences of invalidation-based¶
Once you switch to invalidation-based, several downstream constraints relax:
- Ordering becomes unimportant. An out-of-order invalidation is still just "please refetch"; the refetch yields authoritative state regardless of arrival order. → Figma could break up its single-global-ordered stream into per-shard streams and let invalidations arrive in any order across shards.
- The invalidator can be stateless — it only needs to know the schema, not the set of currently-subscribed queries.
- Optimistic updates simplify — clients don't wait for a full change-propagation round-trip; they invalidate and re-query.
- Invalidations can compress/coalesce — duplicate invalidations for the same key dedupe to one.
Third-order: it unlocks sharding of the invalidator¶
Mutation-based demanded global ordering (apply change A before change B, consistently, everywhere) — which in turn demanded a single global stream. Invalidation-based works shard-by-shard: each DB-shard's invalidator tails its own WAL, emits invalidations; each cache-shard receives only invalidations for its hash range.
→ The architecture becomes natively shardable, removing excessive fan-in and fan-out of the old design.
When mutation-based still wins¶
Not a universal upgrade. Mutation-based remains the right call when:
- Source is very slow / capacity-constrained — refetch cost is prohibitive, pushing the change into the cache is cheaper.
- Cache hit rate is near-100% and fetch cost ≫ update cost — the asymmetry still favors mutate-in-place.
- Strict ordering is cheap because the write volume is low and a single-global-stream is naturally achievable.
- Change payload is trivial (a few bytes) and re-fetch payload is large — the payload asymmetry makes mutation cheaper and simpler.
Figma's old world sat here; the world they're scaling into doesn't.
Precedents / neighbors¶
- CPU cache coherence protocols (MESI etc.) — canonical invalidation-based; a writing CPU invalidates other caches' copies rather than shipping them the new value.
- HTTP cache revalidation (
max-age+ETag/If-None-Match) — time-based invalidation plus conditional re-fetch. - CDN purge APIs — explicit invalidation (
Invalidate /foo) on publish events. - React query libraries (TanStack Query) —
invalidateQueriesAPI mirrors this exactly at app-state-cache granularity. - Materialized views with refresh-on-change (as opposed to incremental-view-maintenance style, which is mutation-based).
Seen in¶
- sources/2026-04-21-figma-keeping-it-100x-with-real-time-data-at-scale — Figma's LiveGraph explicitly names the trade-off and the reason the invalidation-based direction became right as the DB scaled out.
Related¶
- concepts/push-based-invalidation — the client-runtime-level analog (source→dependent graph; mark dirty).
- concepts/change-data-capture — the upstream signal feeding an invalidation-based server cache.
- concepts/read-invalidation-rendezvous — the concurrency correctness contract an invalidation-based cache must uphold.
- concepts/query-shape — the schema discipline that lets invalidations be computed statelessly.
- patterns/stateless-invalidator — the deployment shape enabled by schema-local invalidation computability.