Invalidation Races and Freshness Guarantees

Caching and Failure Recovery · 40 min

System Design · Module 6 · Lesson 3 of 4 · 40 min

Invalidation Races and Freshness Guarantees

Reproduce a stale-fill race, define how quickly updates become visible, and choose a stronger policy when TTL staleness is unacceptable.

Deleting a cached value after a write does not prevent an overlapping old reader from filling it again.

01 The idea

The problem this lesson solves

Reader R misses and obtains v1 from the database.

Prerequisite: earlier lessons introduce the vocabulary and shared examples used here. Follow the module order.

InvalidationRemoving or marking a cached copy unusable after a relevant change.
Stale fillA delayed reader populating an old value after a newer source update.
Version checkComparing an update or fill against a version to reject obsolete work under a coordinated policy.

02 Worked example

Follow one worked example

Invalidation Races and Freshness Guarantees worked diagram01R readsv1 from database02W writesCommit v2 + delete03R fills lateCaches v1 again04Freshness policyBound or reject stalefillRead events in time order, from left to right.Invalidation Races and Freshness Guarantees compact diagram01R readsv1 from database02W writesCommit v2 + delete03R fills lateCaches v1 again04Freshness policyBound or reject stale fill
R reads: v1 from database • W writes: Commit v2 + delete • R fills late: Caches v1 again • Freshness policy: Bound or reject stale fill.

Trace the example in order. Follow the same identities, units and state transitions through the diagram and the events below.

1
R loads old stateReader R misses and obtains v1 from the database.
2
W commits new stateWriter W stores v2 and invalidates the cached key.
3
R fills lateThe older reader stores v1 after the invalidation.
4
Evaluate the contractTTL permits bounded stale serving; immediate visibility requires stronger coordination or bypass.

Deleting a cached value after a write does not prevent an overlapping old reader from filling it again.

03 The method

Mechanisms decisions and a guided tutorial

Work through the explanation in order, then apply it in the tutorial. Each comparison names a cost as well as a benefit.

Choice or conceptPurposeBoundary or cost
Plain cache-asideSimple fast pathOverlapping stale fills remain possible
TTLBounds copy lifetimeDoes not eliminate the race
Version-aware coordinationRejects obsolete work under its contractNeeds authoritative ordering/progress
Bypass sensitive readsUses the stronger authority directlyHigher origin cost

Walk the invalidation race

A reader misses and reads old destination v1 from the database. A writer then commits v2 and deletes the cache entry. The slow reader finally stores v1 into the now-empty cache. ‘Update database, then delete cache’ did not prevent this stale fill. A TTL bounds how long that fill normally survives, but does not eliminate the race. Stronger freshness can require version checks, coordinated fills, invalidation events with suitable ordering, or bypassing the cache for sensitive reads.

Trade freshness for bounded latency explicitly

Stale-while-revalidate serves a bounded stale value while one worker refreshes it. This can work for a public product description whose contract allows brief staleness. It is usually inappropriate for a security revocation check unless the security policy explicitly accepts that delay. Negative caching stores ‘not found’ briefly to reduce repeated misses; creating the previously absent object must invalidate or outlive that negative entry. Return privacy-safe errors regardless of whether the result was cached.

Walk the race event by event

At t1, reader R misses and reads destination v1. At t2, writer W commits v2. At t3, W deletes the cache. At t4, R finishes late and stores v1. The cache now contains v1 even though the writer followed “database first, delete cache second.”

A TTL limits how long this particular cached copy normally survives, but it did not prevent the race. Adding an arbitrary sleep before another delete is not a proof against all delays. State the actual freshness contract before deciding whether bounded staleness is acceptable.

Define the acknowledgment boundary

For a public description, “updates appear within sixty seconds” may be a deliberate product contract. For a revoked permission or changed safety-critical setting, a stale positive decision can be unacceptable. Define whether the guarantee applies to reads starting after update acknowledgment and what happens to reads already in flight.

Stronger designs may bypass cache for sensitive decisions, coordinate version-aware fills, or use an invalidation/update stream with an explicitly reasoned ordering and recovery policy. Merely placing a version number in the value does not help if a stale fill is accepted without comparing it against authoritative progress.

Make negative and stale serving policies explicit

A short-lived negative result can shield the origin from repeated requests for a nonexistent code, but a newly created code may remain hidden until the negative entry is invalidated or expires. An origin timeout should not become a negative cache entry because it did not establish absence.

Stale-while-revalidate deliberately serves an older copy during a bounded grace period while one refresh occurs. It can improve latency for eligible public content. It must not be silently applied to a contract requiring immediate revocation. Freshness is a business guarantee expressed through a serving policy, not a synonym for speed.

Guided tutorial

1. Draw two lanes

Put reader and writer events on a shared timeline.

2. Insert delays

Pause R after the database read and before the cache fill.

3. Write a guarantee

Specify the allowed staleness and update acknowledgment boundary.

4. Choose a policy

Compare bounded TTL with bypass for mutable security-sensitive decisions.

R: cache miss → database returns v1 → pause
W: commit v2 → delete cache → acknowledge update
R: resume → cache.set(v1, ttl)
# Result: stale v1 exists after the delete.
# A stronger contract must reject this fill or avoid this read path.

04 Cheat sheet

Review the decisions

Use this table to explain the mechanism and its limit without memorizing a product name.

Choice or conceptRememberCheck
Plain cache-asideSimple fast pathOverlapping stale fills remain possible
TTLBounds copy lifetimeDoes not eliminate the race
Version-aware coordinationRejects obsolete work under its contractNeeds authoritative ordering/progress
Bypass sensitive readsUses the stronger authority directlyHigher origin cost
InvalidationRemoving or marking a cached copy unusable after a relevant change.
Stale fillA delayed reader populating an old value after a newer source update.
Version checkComparing an update or fill against a version to reject obsolete work under a coordinated policy.

05 Where & why

Check the primary references

Use official references for protocol and implementation details. The questions here are teaching prompts.

Primary reference
Redis eviction policies

Read Redis eviction policies

Primary reference
Redis SET

Read Redis SET

Primary reference
Redis TTL

Read Redis TTL

Primary reference
HTTP stale-while-revalidate

Read HTTP stale-while-revalidate

State the implementation version and the workload assumptions when defending your own design.

06 Interview questions

Explain the reasoning out loud

Answer before opening the explanation. The questions progress from vocabulary to decisions and failure behavior.

What does Invalidation mean?
Removing or marking a cached copy unusable after a relevant change.
What does Stale fill mean?
A delayed reader populating an old value after a newer source update.
What does Version check mean?
Comparing an update or fill against a version to reject obsolete work under a coordinated policy.
Why is delete-after-write insufficient?
A reader can fetch old data before the commit and fill it after the delete. The timeline contains no step that rejects that late work.
Does a version field alone solve the race?
No. The fill path must compare against trusted newer progress and reject obsolete data. An unchecked label changes no coordination behavior.
When is bounded staleness acceptable?
When the product explicitly permits it for that operation and the failure behavior stays within the contract. Public descriptions and security revocations can have very different policies.
What must a revocation guarantee specify?
Its acknowledgment boundary, eligible reads, in-flight behavior and recovery path. “Immediately” is ambiguous without those details.
How can negative caching hide a new object?
A retained not-found result can outlive the source’s absence. Creation should invalidate it or the contract must accept its bounded lifetime.

07 Practice problems

Practice a new case

Solve the new cases before revealing hints. Complete worked solutions follow the set.

Hit rate

Easy
Compute the hit rate for 18 hits and 6 misses.
Follow-up
Use total lookups as the denominator.
Show the hint
Hits plus misses equals the total.

Name the race

Easy
Identify the stale operation in read v1, commit v2, delete, fill v1.
Follow-up
State the assumption or edge case that makes the answer correct.
Show the hint
The last event uses an earlier observation.

Staleness bound

Medium
A stale fill occurs at t=20 with TTL 30 seconds; state its normal expiry time under the simple model.
Follow-up
State the assumption or edge case that makes the answer correct.
Show the hint
TTL starts from insertion, not the earlier source read.

Negative create

Medium
A key is negatively cached for a minute, then created ten seconds later; explain the visibility risk.
Follow-up
State the assumption or edge case that makes the answer correct.
Show the hint
The cached absence remains a copy too.

Version proof

Medium
Review “we put updated_at in the cached JSON, so stale fills are impossible.”
Follow-up
State the assumption or edge case that makes the answer correct.
Show the hint
Ask which atomic check rejects older work.

Correct redirect update

Hard
Design a cache policy for links whose destination changes must be visible immediately after update acknowledgment.
Follow-up
A reader may have fetched an old value before the update.
Show the hint
A TTL only bounds staleness; it does not prevent the late fill.

Worked solutions

1. Hit rate

18 / (18 + 6) = 0.75, or 75 percent.

2. Name the race

The late fill writes old v1 after the newer commit and invalidation.

3. Staleness bound

It normally expires at t=50, not thirty seconds after the writer’s update. Other stale-serving or refresh policies can change the full contract.

4. Negative create

Readers can see not found for the remaining negative lifetime unless creation invalidates it or another freshness mechanism intervenes.

5. Version proof

The claim is incomplete. The fill must compare against trustworthy newer state under a coordinated protocol; storing a timestamp without enforcing ordering does not reject a stale write.

6. Correct redirect update

Do not promise immediate freshness with plain cache-aside and TTL. Read through a coordinated version-aware mechanism that rejects stale fills and defines concurrent-read ordering, or bypass the cache for these mutable links. State the acknowledgment boundary and handle already-in-flight reads explicitly.