Skip to content

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)invalidateQueries API 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

Last updated · 200 distilled / 1,178 read