WEB / WEB-19.01
Application cache and invalidation
Follow repeated public-note reads with cache-aside; separate hits, misses, errors, and authoritative revision ownership.
1. Core · Mechanism
Read every scenario without motion
Miss then hit
- Public read intent
The application starts a public Tea note read. Application, cache copy and authoritative note store are local components in this process; neither the store read nor cache fill has happened yet.
- Application
- Public note read
- Own local cache
- No cache result
- Authoritative owner
- Authoritative Tea note; version1
- Result decision
- No result
- Cache miss
No local entry exists; the miss does not prove absence in the authoritative store.
- Application
- Public note read
- Own local cache
- Miss; no cached value
- Authoritative owner
- Authoritative Tea note; version1
- Result decision
- No result
- Authoritative read
The store returns version1/title and readCount becomes1; this counter is not a latency benchmark.
- Application
- Public note read
- Own local cache
- Miss; no cached value
- Authoritative owner
- Tea note/version1; readCount1
- Result decision
- No result
- Populate copy
A separate public-note copy enters the cache. Later mutation of the object returned to the application leaves the stored cache copy unchanged; applicationMutationChangesCache=false observes that boundary.
- Application
- Public note read
- Own local cache
- Cached Tea note/version1
- Authoritative owner
- Tea note/version1; readCount1
- Result decision
- No result
- First result
The application uses the public note from the first miss; HTTP arrival is not modeled here.
- Application
- Public note read
- Own local cache
- Cached Tea note/version1
- Authoritative owner
- Tea note/version1; readCount1
- Result decision
- First result Tea note/version1
- Later cache hit
A later selected read finds the cached copy; store readCount stays1 without a general newest-state guarantee.
- Application
- Public note read
- Own local cache
- Hit Tea note/version1
- Authoritative owner
- Tea note/version1; readCount1
- Result decision
- First result Tea note/version1
- Reused result
The same public-note task completes from a cache hit; no production performance measurement is made.
- Application
- Public note read
- Own local cache
- Hit Tea note/version1
- Authoritative owner
- Tea note/version1; readCount1
- Result decision
- Reused Tea note/version1
Late old fill
- Public read intent
The application starts a public-note read; the local cache is a copy and the store is the authoritative owner.
- Application
- Public note read
- Own local cache
- No cache result
- Authoritative owner
- Authoritative Tea note; version1
- Result decision
- No result
- Old read prepared
An old reader has prepared candidate version1; its cache fill is not yet a committed result.
- Application
- Pending fill: Tea note/v1
- Own local cache
- No cache result
- Authoritative owner
- Authoritative Tea note; version1
- Result decision
- No result
- Store commit
The authoritative owner commits Coffee note/version2; the pending old candidate does not update itself.
- Application
- Pending fill: Tea note/v1
- Own local cache
- No cache result
- Authoritative owner
- Authoritative Coffee note; version2
- Result decision
- No result
- Invalidate copy
The cache entry is invalidated with known owner revision2; other process copies are not assumed synchronized.
- Application
- Pending fill: Tea note/v1
- Own local cache
- Empty cache; known revision2
- Authoritative owner
- Authoritative Coffee note; version2
- Result decision
- No result
- Old candidate
The pending version1 candidate reaches the local fill boundary; selected owner current revision is2.
- Application
- Candidate1 vs current2
- Own local cache
- Empty cache; known revision2
- Authoritative owner
- Authoritative Coffee note; version2
- Result decision
- No result
- Reject old fill
One serialized condition rejects unequal revisions; the cache stays empty with no distributed guarantee.
- Application
- Candidate1 vs current2
- Own local cache
- Rejected1; empty cache
- Authoritative owner
- Authoritative Coffee note; version2
- Result decision
- No result
- Fresh read
A fresh authoritative read gets version2; committed state replaces the rejected old-fill candidate.
- Application
- Candidate1 vs current2
- Own local cache
- Rejected1; empty cache
- Authoritative owner
- Fresh Coffee note; version2
- Result decision
- No result
- Fresh result
The application uses a version2 result; the sequence is verified only in selected local adapters.
- Application
- Candidate1 vs current2
- Own local cache
- Rejected1; empty cache
- Authoritative owner
- Fresh Coffee note; version2
- Result decision
- Coffee note/version2
Cache error
- Public read intent
The application starts a public-note read; the local cache is a copy and the store is the authoritative owner.
- Application
- Public note read
- Own local cache
- No cache result
- Authoritative owner
- Authoritative Tea note; version1
- Result decision
- No result
- Cache error
The injected unavailable cache raises an error; it is neither a miss nor authoritative-record absence.
- Application
- Public note read
- Own local cache
- Error; no cache result
- Authoritative owner
- Authoritative Tea note; version1
- Result decision
- No result
- Explicit fallback
The selected public-read policy falls back to its authoritative owner; it is not assumed for every operation.
- Application
- Public note read
- Own local cache
- Error; no cache result
- Authoritative owner
- Fallback Tea note/version1
- Result decision
- No result
- Populate fails
The cache remains unavailable so population success is not claimed; the authoritative result copy is retained.
- Application
- Public note read
- Own local cache
- Unavailable; not populated
- Authoritative owner
- Fallback Tea note/version1
- Result decision
- No result
- Store-owned result
The application uses its authoritative public result; an owner error would create no empty/default note.
- Application
- Public note read
- Own local cache
- Unavailable; not populated
- Authoritative owner
- Fallback Tea note/version1
- Result decision
- Tea note/version1; source=store
ACTORS · STATE · DATA FLOW
Which work does a cache avoid?
An application cache reuses computed results or data read from another owner to reduce repeated work. The first public Tea note read misses: the application reads version=1/title from the authoritative store, copies it into the cache, and uses it. A later hit returns the selected value without another store read. Our fixture observes this with a read counter, not latency measurements or a claim that every workload becomes faster. Query results, computed views, and raw objects are different caching units; we select one public note.
This is separate from browser HTTP caching. A process-local dictionary gives different application instances their own copies. An in-memory service such as Redis can instead be a shared cache owner over a network; memory storage or a Redis label does not guarantee persistence or consistency. Redis server-assisted client-side caching can manage application-memory copies through invalidation notifications. The native adapter here stays in one process and executes no Redis connection or tracking.
A hit finds a cached result for a selected key; it does not generally prove the newest authoritative state. A miss does not prove database absence and needs an authoritative read. An error means cache access failed. This public-read fixture explicitly falls back to the authoritative store on errors; if that owner is unavailable too, it creates no empty note. Error policy is application-specific and is not automatically appropriate for sensitive or state-changing operations.
Which revision does invalidation protect?
A cache-aside write commits the authoritative update before invalidating the corresponding entry. Invalidating before commit lets an intervening reader refill the previous committed value. Even the intended order does not guarantee consistency under every concurrent or topology condition. External writers, delayed reads, and other local caches can reintroduce old data. TTL can bound staleness, but does not guarantee the newest value before expiry.
In the stale-fill case a reader has prepared version=1 when the owner commits version=2 and invalidation occurs. The old fill candidate then returns. One serialized adapter checks its known current revision=2 and rejects candidate=1; the next read obtains version=2. This authored version guard applies only to that owner access/update contract, not distributed consistency in general. Redis documentation separately addresses invalidations racing with delayed read responses; our local guard does not execute that protocol.
2. Red · Trust & Impact
Read every scenario without motion
Old fill
- The old read has not finished
The reader has prepared the Tea note result at version=1. This copy has not entered the cache and will not update itself when the owner changes.
- Pending reader
- candidateVersion=1
- Authoritative note owner
- Tea note / version=1
- Cache admission boundary
- Fill pending
- The owner committed a new revision
The authoritative record is now Coffee note at version=2. The pending reader still carries its old copy; one owner’s commit does not automatically change another state.
- Pending reader
- candidateVersion=1
- Authoritative note owner
- Coffee note / version=2
- Cache admission boundary
- Fill pending
- The cache is empty; the old candidate remains
Invalidation removed the cached entry. Here the admission boundary knows owner revision=2. An empty cache does not mean every pending read is fresh.
- Pending reader
- candidateVersion=1
- Authoritative note owner
- Coffee note / version=2
- Cache admission boundary
- knownCurrentVersion=2 / cache empty
- Compare both revisions in one decision
The old candidate’s version=1 does not match currentVersion=2 at admission. The decision uses the revision actually carried by the candidate.
- Pending reader
- candidateVersion=1
- Authoritative note owner
- Coffee note / version=2
- Cache admission boundary
- 1 != 2 / fill decision pending
- The old copy was rejected
With accepted=false, the old fill does not alter the cache, which remains empty. This guard applies to this serialized owner boundary where the current revision is known.
- Pending reader
- candidateVersion=1 / rejected
- Authoritative note owner
- Coffee note / version=2
- Cache admission boundary
- accepted=false / cache empty
- A new read returns to the current owner
The next read returns version=2. Rejecting the old fill and obtaining a new result are separate observations. This does not establish synchronized copies in other processes.
- Pending reader
- nextResultVersion=2
- Authoritative note owner
- Coffee note / version=2
- Cache admission boundary
- old fill accepted=false
Owner also down
- The public read awaits a result
The application requests the public note. Its cache copy and authoritative record have separate owners; the result source is not yet known.
- Pending reader
- Public note result pending
- Authoritative note owner
- Not read yet
- Cache admission boundary
- Cache outcome pending
- Cache access failed
Cache outcome=error is different from a miss. The policy for this public read will attempt an authoritative read.
- Pending reader
- Fallback decision pending
- Authoritative note owner
- Not read yet
- Cache admission boundary
- cacheOutcome=error
- The fallback owner is explicit
The application’s fallback source is the authoritative store. It consults the record owner instead of inventing a note from the failed cache.
- Pending reader
- fallbackOwner=authoritative store
- Authoritative note owner
- Read result pending
- Cache admission boundary
- cacheOutcome=error
- The authoritative read also failed
When the second owner is unavailable too, no validated note result is available. Treating the cache error as data absence does not resolve that gap.
- Pending reader
- No successful result
- Authoritative note owner
- Store unavailable
- Cache admission boundary
- cacheOutcome=error
- No default note is created
bothUnavailable.result=error and createdDefaultNote=false. The application does not turn an incomplete read into a newly created empty record.
- Pending reader
- result=error / createdDefaultNote=false
- Authoritative note owner
- Store unavailable
- Cache admission boundary
- cacheOutcome=error
- Preserve the policy scope
This decision belongs to the public read-only note example. Applying the same fallback to private data or all state-changing operations requires separate policy evidence.
- Pending reader
- result=error / no default
- Authoritative note owner
- No authoritative result obtained
- Cache admission boundary
- Public read policy
ASSUMPTION · BOUNDARY · CONTROL
A hit and freshness are different claims
Do not review a controlled cache using hit ratio alone. The artifact separates cache and authoritative versions: a stale copy can still be a hit. Returning an older public title is a mechanism condition, not private-data or policy-bypass reproduction. Identify the caching unit and allowed staleness. TTL must not substitute for newest state or a permission refresh.
Deleting one instance local copy does not update another instance. A shared cache does not settle every application copy or replica either. The risk condition is interpreting one owner completion as synchronization across all owners. The effect can be an incorrect revision or stale computed view. Only public notes are used; the native adapter measures no distributed invalidation or production workload.
Do not convert errors into database absence
An unavailable cache returns an error, not a miss. If the public-read fallback obtains an authoritative result, the application uses that owner and does not require successful cache population. If the authoritative owner is unavailable too, an error remains and no empty note is created. This selected policy does not prove all endpoints operate safely during outages; resource consumption and retry storms need different evidence.
Ordinary concurrency such as a pending old fill after invalidation can restore an older revision. The fixture observes unequal current-owner and candidate revisions and preserves state. The expected truth table is examined without running an attack by disabling a guard. Missing observations do not prove a clean cache: a zero selector count without a source event is insufficient evidence.
3. Blue · Secure development & defense
Read every scenario without motion
Fallback result
- Separate fields by owner
The evidence producers are the local cache adapter and note-store reads. error, returnedVersion and readCount are fields of this example, not predefined Redis log fields. This review uses the error section. Cache outcome, fallback owner, returned version and population outcome answer different questions.
- Cache observation
- error / fields selected
- Result source
- Result source not read yet
- Evidence interpretation
- Interpretation pending
- Distinguish an error from a miss
cacheOutcome=error shows unsuccessful cache access. Counting it as a miss would hide which component failed.
- Cache observation
- cacheOutcome=error
- Result source
- Result source not read yet
- Evidence interpretation
- Cache failure; not absence
- Read the fallback owner
fallbackOwner=authoritative store. A successful result therefore comes from the record owner, not the cache.
- Cache observation
- cacheOutcome=error
- Result source
- fallbackOwner=authoritative store
- Evidence interpretation
- Result version pending
- The returned revision is separate evidence
returnedVersion=1 identifies this read result. The number alone does not show repopulation or refreshed copies in other processes.
- Cache observation
- cacheOutcome=error
- Result source
- returnedVersion=1 / source=store
- Evidence interpretation
- Owner result exists; cache outcome separate
- Population did not succeed
cachePopulationSucceeded=false remains. The application can return a note from its owner while the cache is still unavailable; these are different success criteria.
- Cache observation
- cachePopulationSucceeded=false
- Result source
- returnedVersion=1 / source=store
- Evidence interpretation
- Read success != cache recovery
- Choose the supported claim
This record supports an authoritative-store result for this public read. It provides no evidence of cache recovery, latency gains or resilience under every outage condition.
- Cache observation
- cacheOutcome=error / population=false
- Result source
- returnedVersion=1 / source=store
- Evidence interpretation
- Public read fallback verified
Observation gap
- Select the stale-fill record
The evidence producers are the local cache adapter and note-store reads. error, returnedVersion and readCount are fields of this example, not predefined Redis log fields. The teaching record compares candidateVersion with currentVersion. The admission claim also needs a fillAccepted observation.
- Cache observation
- candidateVersion=1 / currentVersion=2
- Result source
- fillAccepted not read yet
- Evidence interpretation
- Admission result unknown
- The admission field is missing here
In this incomplete-observation branch, fillAccepted is not supplied. Filling it with false would substitute an assumption for evidence.
- Cache observation
- candidateVersion=1 / currentVersion=2
- Result source
- fillAccepted=unknown
- Evidence interpretation
- Admission result unknown
- The selector only counts what it sees
A selector for accepted stale revisions may find no match. With the admission field absent, zero matches are not evidence of rejection.
- Cache observation
- candidateVersion=1 / currentVersion=2
- Result source
- fillAccepted=unknown / no matching record
- Evidence interpretation
- Zero matches != proven reject
- Identify the missing observation
Request admission outcome and the current owner revision used for the same fill attempt. A different attempt’s result cannot complete this one.
- Cache observation
- candidateVersion=1 / currentVersion=2
- Result source
- Decision missing for this fill
- Evidence interpretation
- Same-attempt admission evidence required
- Compare with the complete example
The complete lateFill record contains accepted=false and cacheEmptyAfterReject=true together. Its conclusion cannot be copied into an incomplete branch as if the same event had been observed.
- Cache observation
- candidateVersion=1 / currentVersion=2
- Result source
- complete example: accepted=false
- Evidence interpretation
- Missing branch remains unknown
- Retain uncertainty in the conclusion
The incomplete branch ends with insufficient evidence. It proves neither correct nor incorrect cache behavior and keeps the missing field explicit.
- Cache observation
- candidateVersion=1 / currentVersion=2
- Result source
- fillAccepted=unknown
- Evidence interpretation
- Insufficient evidence
IMPLEMENTATION · SECURITY CONTROL · VERIFICATION
Make caching contracts observable
The evidence producers are the local cache adapter and note-store reads. error, returnedVersion and readCount are fields of this example, not predefined Redis log fields. Record which public representation and revision a cache key describes, and return a copy to the application. The first miss makes authoritative readCount=1; a later hit leaves it unchanged. These counts describe selected adapter operations, not latency or Redis statistics. Expiry, eviction, and missing retain the previous key-value lesson boundaries. The artifact makes clear that the cache does not own authoritative records.
The fallback is selected for a read-only public scenario, so cache and store errors yield separate observations. The ledger records the result owner when cache access fails and verifies that authoritative failure creates no default object. Actual applications also need purpose, permissions, and allowed staleness. HTTP Cache-Control does not automatically invalidate this server-side dictionary; the policies of these two cache layers differ.
Check commit, invalidation, and refill separately
The fixture commits version=2/title in the store, invalidation removes the entry, and the next read populates version=2. Each owner state is visible; successful invalidation is not an atomic cross-owner transaction. The same serialized guard rejects old pending candidate=1, leaving the cache empty until a fresh version=2 read. Observer reads verify this deterministic native sequence.
The guard requires a current revision within the same decision boundary. Different processes, delayed notifications and independent writers need their own scope design. An absent decision observation remains UNKNOWN; another request’s successful result cannot supply it.
NOW APPLY IT TO THE CASE
Which revision is carried by an old read result after cache invalidation?
Duru reads the public Tea note in Atlas. While the record owner changes its title to Coffee note, an earlier read still carries version=1. Invalidating the cache does not update that pending copy.
Which cache owner supplies the case representation? Evaluate key scope, freshness and viewer context together; a hit does not establish the correct viewer.
Open the related case file ↗READ THE EXAMPLE TOGETHER
Public note cache/revision ledger
Select a field and examine what it supports and where the evidence ends.
Read the raw artifact and field explanations
{
"selectedPolicy": {
"cache": "own process-local copy dictionary",
"authoritativeStore": "own public-note memory adapter",
"redisExecuted": false,
"httpCache": false,
"errorFallback": "authoritative read for public read only",
"revisionGuard": "serialized known owner revision; not distributed guarantee"
},
"normal": {
"firstResult": {
"id": "tea",
"title": "Tea note",
"version": 1
},
"firstCacheOutcome": "miss",
"storeReadsAfterFirst": 1,
"secondCacheOutcome": "hit",
"storeReadsAfterSecond": 1,
"applicationMutationChangesCache": false
},
"update": {
"committedValue": {
"id": "tea",
"title": "Coffee note",
"version": 2
},
"order": [
"store commit",
"cache invalidate",
"next read/refill"
],
"cacheEmptyAfterInvalidate": true,
"refillVersion": 2
},
"lateFill": {
"candidateVersion": 1,
"knownCurrentVersion": 2,
"accepted": false,
"cacheEmptyAfterReject": true,
"nextResultVersion": 2
},
"error": {
"cacheOutcome": "error",
"fallbackOwner": "authoritative store",
"returnedVersion": 1,
"cachePopulationSucceeded": false
},
"bothUnavailable": {
"result": "error",
"createdDefaultNote": false
},
"selector": {
"language": "XPath",
"xml": "<audit><cache candidateVersion=\"1\" currentVersion=\"2\" fillAccepted=\"false\"/></audit>",
"xpath": "count(/audit/cache[@candidateVersion != @currentVersion and @fillAccepted='true'])",
"expected": 0,
"positiveFixtureXml": "<audit><cache candidateVersion=\"1\" currentVersion=\"2\" fillAccepted=\"true\"/></audit>",
"positiveExpected": 1,
"scope": "Only authored protocol-decision observations, not product log field names or a live HTTP capture.",
"missingFieldBehavior": "Missing observation is insufficient evidence; zero matches is not proof of correct runtime behavior."
}
}- normal
- Authoritative read counts for miss/hit.
- update/lateFill
- Commit/invalidate/refill and serialized revision guard.
- error/bothUnavailable
- Error fallback owner and absence of default state.
- selector
- Synthetic stale-fill acceptance decision.
Own local memory adapters, not Redis/ElastiCache, latency, or cluster measurements. The guard current-owner assumption is explicit.
Two reality checks
A cache hit proves the newest authoritative state.
A hit finds a cached result; freshness depends on a separate lifetime, invalidation, and consistency contract.
Invalidation prevents every old pending read from refilling the cache.
A selected concurrent sequence can return an old fill; a local condition needs an explicit revision-owner scope.
YOUR TURN
Work through this story.
Predict the outcome, find the evidence boundary, and explain it in your own words.
Known current version is 2 but a late-fill candidate is 1. Is it accepted into the cache?
- No; cacheEmptyAfterReject=true and the next read returns version 2.
- Yes; cache filling always overwrites with the last completed request.
Read the explained answer
No; cacheEmptyAfterReject=true and the next read returns version 2. — The selected serialized owner prevents a stale candidate from obscuring new state.
Why does public-read fallback after a cache error differ from both-unavailable?
Selected lesson artifact fields; teaching example
- error
- {"cacheOutcome":"error","fallbackOwner":"authoritative store","returnedVersion":1,"cachePopulationSucceeded":false}
- bothUnavailable
- {"result":"error","createdDefaultNote":false}
- Every cache error is a cache miss that creates a default resource.
- An authoritative read is possible after a cache error; when both are unavailable, the result is error with no default note.
Read the explained answer
An authoritative read is possible after a cache error; when both are unavailable, the result is error with no default note. — ErrorFallback is a selected public-read policy; data is not invented for unknown authoritative state.
Why do store commit→cache invalidate→refill ordering and process-local scope not establish distributed invalidation guarantees?
Read the explained answer
The selected local dictionary is invalidated after the canonical-store write and refilled with version 2 on the next read. Its revision guard uses known serialized-owner state; it measures neither inter-node invalidation delivery nor replication. Application cache is not browser HTTP cache, and RedisExecuted=false.
□ I did not count a local guard as a distributed guarantee.
□ I separated application cache from HTTP cache.
Open the application console · request / response / browser
Also open request, URL, cookie and DOM examples
Sources and scope
- Microsoft Learn — Cache-Aside patternContext and problem · Solution · Problems and considerations
- Amazon ElastiCache — Caching strategies for MemcachedLazy loading · Advantages and disadvantages of lazy loading · Adding TTL
- Redis — Client-side caching referenceClient-side caching reference · There are two hard problems in computer science... · The Redis implementation of client-side caching · Avoiding race conditions
- Redis — GETGET command description · Return information
- Redis — Key evictionUsing the maxmemory configuration directive · Eviction policies