Skip to content
About
Main content

Freshness bounding

What does each way of bounding staleness cost, and why is a TTL the floor under invalidation?

Dimension B: freshness bounding

Nothing. This is valid only for , content-addressed data. It is the best cell in the whole matrix, for reasons the matrix below sharpens.

TTL. Its cost is the one people miss: a forces a miss for every in every period, whether or not anything changed.

That is exactly backwards from what you want. It also gets worse as the grows at a fixed TTL. (This is the rule that a fixed rate or width meets exponential growth and fails silently: here a fixed period meets a growing population.) Verification is the fix. This is why HTTP has both Expires and ETag: they price freshness in different units, one per period and one per round trip.

Versioned or content-addressed keys never need invalidation, because the version is part of the key. Staleness then reduces to the staleness of one small pointer ("which version is current?").

Explicit invalidation has three costs:

  1. Fan-out: the message must reach every instance and every tier.
  2. Dependency-set knowledge: you must know which entries depend on the changed data (the unsolved problem from The derivation ladder).
  3. Delivery. This one is decisive: invalidation by message is only as sound as message delivery, so one lost invalidation means unbounded staleness.

So a TTL is not an alternative to invalidation.

Leases and verification give bounded staleness with no wasted payload. You pay either in server-side state, or in a round trip on each read.

The matrix, and the cells that must stay empty

Immutable keyTTLVersioned + pointerExplicit invalidateLease / revalidate
not applicable: writes make new keysredundant for your own writes; your only defence against writers you do not know aboutcommon: bump the pointer on commitnatural fit, still needs a TTL floorover-engineered: coherence is already paid for
the best cell in the whole subjectthe global default, and the race above is whythe lakehouse standardthe standard web stackideal when payloads are much larger than a round trip
incoherentmust be empty: expiring an authoritative entry is data lossonly with a flush logthe only sound columnonly over a durable dirty set

Three things fall out of the matrix.

First, a TTL's real job is to bound the staleness caused by write paths you do not know about. That is the defining constraint of caching, written as a configuration value. If you truly control every writer, a TTL is waste. The moment a second writer exists, the TTL is the only thing protecting you, and you cannot tell from inside the cache which of the two worlds you are in.

Second, the immutable-key column makes Dimension A irrelevant. Writes create new keys, so there is no routing decision. Immutability does not only remove invalidation. It removes a whole axis of the design space. This is the strongest form of the immutability dividend.

Third, the empty cell is a warning. Never give a write-back cache a TTL that expires dirty entries.

Compression: Routing (through, around, back) and freshness (nothing, TTL, version, invalidate, lease) are independent. Delete rather than update. Always keep a TTL floor. Immutability deletes the routing dimension outright.