Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Cache-Aside Fill Races and Version Guards

Last updated: 7 Oct 20268 min read
tutorial
IntermediateBy AITrove Editorial

An application cache is a disposable copy of data held by an authoritative store. In cache-aside reads, the application checks the cache, loads the source on a miss, and saves the result for later requests. Writes usually commit to the source and then invalidate or replace the copy. That sequence is not automatically consistent. A read can miss, fetch an older version, pause, and write it into the cache after a newer transaction commits and invalidates the key. Time-to-live eventually removes the stale copy but does not protect the intervening requests. A version guard, a carefully ordered invalidation scheme, or a decision to bypass the cache for critical reads must define the actual freshness contract.

Working case

Reviewer 47 opens permit case 62 while its summary key is absent. The read obtains version 29 from the database, then pauses before filling the cache. Reviewer 91 approves the case, committing version 30 and deleting the cached summary. The paused read finally writes version 29 into the now-empty slot. Every later reader sees an obsolete open state until expiry. The fix advances a separate version floor to 30 after commit and rejects any fill below that floor, even if no value is cached. The approval response reads version 30 from the source; the broader list may tolerate a bounded lag. The cache remains a performance copy, not an approval authority.

Implementation boundary

javascript
function shouldFillCase(versionFloor, cachedVersion, loadedVersion) {
  return loadedVersion >= versionFloor && (cachedVersion === null || loadedVersion >= cachedVersion);
}
console.log(shouldFillCase(30, null, 29));
// Output: false

Name the source of truth and the freshness limit for each read path before selecting a cache policy. Give mutable records monotonic versions or another token that can be compared under the source's concurrency model. After a committed write, advance a version floor that remains visible even when the cached value is absent; retain it longer than any in-flight fill. An atomic fill compares the loaded version with that floor and with any cached value before storing. A separate get followed by set is not enough. There is still a stale-read window between source commit and floor publication; do not claim immediate consistency across independent stores. If the floor update fails, bypass the cache for critical reads and rely on bounded expiry or reconciliation for tolerant views. For approval or permission, read the authoritative state. Scope keys by tenant, representation, and authorization dimensions, then measure stale returns through sampled source comparisons.

Cost and boundaries

A cache hit can avoid a database query, but every miss still pays source latency and cache network work. Version floors add storage per mutable key and an atomic fill step; their retention must cover delayed fills. Broad invalidation may discard useful entries, causing a refill burst; narrow invalidation requires a complete dependency map. A short TTL bounds stale lifetime but raises miss rate. An indefinitely long TTL saves reads while making a missed invalidation expensive. Track hit rate, fill duration, rejected stale fills, source-versus-cache version gap, floor publication failure, and extra source reads for critical routes. A hit-rate percentage alone cannot show whether the cache returned correct data.

Failure trace

Pause a read after loading version 29, commit version 30, publish the version floor, invalidate, then resume the old fill into an empty value slot. The old version must be rejected. Drop the floor update after a committed write and verify the system reports a degraded freshness guarantee rather than claiming strict consistency. Restart one app instance with an empty local cache while another retains a copy; both should obey the same authorization rule. Reassign the permit to another tenant and ensure the prior tenant cannot access a cached summary through an old key. Disable the cache entirely and prove the source-backed path remains correct within its capacity budget.

Verification

  • A late old fill cannot enter an empty slot after a newer version floor is published.
  • Critical permission and approval paths use authoritative state.
  • Version-floor publication failure has a bounded freshness or reconciliation path.

Practice drill

Model case 62 with versions 29 and 30. Interleave one read fill and one approval commit so the fill completes last. Record what every reader can see before, during, and after the version-floor update. Implement an atomic compare-by-version fill rule and test equal-version replacement, older fill rejection into an empty value slot, missing floor, and cache outage. Set a freshness limit for a public count and a stricter source check for approval permission. Report hit rate together with observed stale-version gap rather than presenting one number as proof of correctness.

Decision note

A cache copy needs an explicit freshness contract and a race-safe rule for filling after writes.

Common Mistakes

  • Assuming delete-after-write or comparison with a missing value blocks old fills.
  • Treating TTL as proof that no stale response is served.
  • Using cached ownership as the final authorization decision.

Related lessons

Application Cache Consistency and Capacity; Cache Stampede, Singleflight, and Hot-Key Budgets; Cache Memory, Eviction, and Degraded Read Paths; Negative Cache Entries and Stale-Read Contracts; Transactions and Concurrent Writes; Tenant Scope in Cache and Background Work.

Apply and check

Build Project: permit cache recovery and consistency and review Web Development: application cache contracts quiz.

web-tech
web-development
Storage details