Testland
Browse all skills & agents

cache-coherence-patterns-reference

Pure-reference catalog of cache-coherence patterns across the request path. Covers the canonical RFC 9111 directives (Cache-Control: max-age, s-maxage, no-cache, no-store, must-revalidate, private/public, immutable; Vary for key derivation; ETag + If-None-Match revalidation), the layered-cache discipline (browser → CDN → reverse-proxy → application → data store), per-tier coherence patterns (write-through, write-back, write-around, cache-aside), and the canonical invalidation strategies (TTL-only, event-driven purge, surrogate keys, version-tagged keys). Use for pattern selection, Cache-Control header design, and coherence audits; use cache-key-collision-detector when the question is whether two requests in an existing system collide on a concrete key scheme. Consumed by redis-cache-tests, cdn-cache-purge-tests, varnish-test-vtc-syntax, browser-cache-control-tests, cache-key-collision-detector.

cache-coherence-patterns-reference

Overview

Cache coherence is the discipline of keeping cached values consistent with their source of truth across multiple tiers (browser, CDN, reverse-proxy, application, data store). It is the "C" in "two hard things in computer science" - wrong coherence shows as stale data, wrong invalidation shows as cache stampedes per cache-stampede-reference.

This skill is a pure reference consumed by per-tier test skills.

When to use

  • Designing the cache tiers for a new product / endpoint.
  • Auditing an existing cache for coherence bugs (stale reads after writes, cross-tenant cache leaks, layered TTLs that fight each other).
  • PR review of changes to cache headers, Vary, or invalidation triggers.
  • Investigating "users see stale data" reports.

The five-tier stack

TierWhereCommon TTLInvalidation
BrowserCache-Control: privateminutes-hoursTTL only (or Service Worker code)
CDNCloudflare / Fastly / CloudFront / Akamaiseconds-daysPurge API or surrogate-key tag
Reverse proxyVarnish, nginxseconds-hoursVCL purge / nginx cache_purge
ApplicationRedis / Memcached / in-processseconds-minutesDirect delete / pub-sub broadcast
Data storePostgres query cache, RDS read replicassecondsReplication-driven

A coherence bug at any tier surfaces at the user. The test surface is layered; each tier needs its own coherence tests.

RFC 9111 directives - the contract layer

Per www.rfc-editor.org/rfc/rfc9111.html:

Response directives (server → cache)

DirectiveRFC refMeaning
max-age=N§5.2.2.1"The response is to be considered stale after its age is greater than the specified number of seconds."
s-maxage=N§5.2.2.10"For a shared cache, the maximum age specified by this directive overrides... max-age."
no-cache§5.2.2.4"The response MUST NOT be used to satisfy any other request without forwarding it for validation."
no-store§5.2.2.5"A cache MUST NOT store any part of either the immediate request or the response."
must-revalidate§5.2.2.2"Once the response has become stale, a cache MUST NOT reuse that response... until it has been successfully validated."
private§5.2.2.7"A shared cache MUST NOT store the response (intended for a single user)."
public§5.2.2.9"A cache MAY store the response even if it would otherwise be prohibited."
immutableRFC 8246Response body will not change for the lifetime of the URL. Browsers skip revalidation.

Per RFC 9111 §4.2.4: "A cache MUST NOT generate a stale response unless it is disconnected or doing so is explicitly permitted by the client or origin server." This is the formal basis for stale-while-revalidate per stale-while-revalidate-reference.

Vary - the cache key

Per RFC 9111 §4.1: "When a cache receives a request that can be satisfied by a stored response and that stored response contains a Vary header field, the cache MUST NOT use that stored response without revalidation unless all the presented request header fields nominated by that Vary field value match those fields in the original request."

Practical: Vary: Accept-Encoding, Authorization means "separate cache entries per (Accept-Encoding, Authorization) combination." Missing Vary: Authorization is the canonical cross-tenant cache leak per qa-multi-tenancy/cross-tenant-data-leak-tests Test 10.

ETag + If-None-Match revalidation

Per RFC 9111 §4.3.1: "Another validator is the entity tag given in an ETag field. One or more entity tags can be used in an If-None-Match header field for response validation."

Pattern: server returns ETag: "abc123"; client sends If-None-Match: "abc123"; server returns 304 Not Modified or 200 OK with new ETag. Bandwidth-efficient but doesn't help latency (still a round-trip).

Cache-writing patterns

For application-tier caches (Redis):

PatternFlowWhen
Cache-aside (lazy load)Read miss → read source → populate → return; Write → invalidate cacheRead-heavy, eventual consistency OK
Write-throughWrite → write source → write cache (synchronous)Strong consistency, latency tolerable
Write-backWrite → write cache → async write to sourceBurst writes; data-loss risk on cache crash
Write-aroundWrite → write source (skip cache); reads do cache-asideWrite-heavy with rare re-reads
Refresh-aheadBackground refresh before TTL expiresPredictable read patterns; hot keys

Invalidation strategies

StrategyMechanismTrade-off
TTL-onlyJust let it expireSimple; possibly-stale window = TTL
Event-driven purgeSource-of-truth update fires a deleteCoupling; firehose at high write rate
Surrogate keys (Fastly, Varnish)Tag responses; purge by tagGroup-invalidation; coordination cost
Version-tagged URLs/api/users?_v=42; new version = new keyImmutable cache; full deploy per change
Soft purgeMark stale, keep serving until refreshUsed by stale-while-revalidate per stale-while-revalidate-reference

Cross-tier coherence problems

ProblemWhereDetection
Browser caches stale page after server purgeBrowser ignores must-revalidate, or no must-revalidateE2E test: write → reload → see old
CDN serves stale after origin updatePurge didn't propagate or s-maxage too longE2E: write → purge → read at CDN edge
Different Vary at browser vs CDNCDN strips headers; cache keys divergeHeader-comparison test
Layered TTL inversions-maxage < max-age → CDN refreshes more often than browser; browser eventually outpaces CDNAudit the TTL stack
Vary: Cookie without normalised cookiesTracker cookies fragment cache; near-zero hit rateInspect Vary; normalise
Tenant-scoped data with shared VaryCross-tenant leak per qa-multi-tenancy/cross-tenant-data-leak-testsAdd Authorization to Vary or use private

Testable behaviours by tier

TierTest categories
BrowserCache-Control respected (max-age, no-cache, must-revalidate); ETag round-trip; Vary honoured
CDNEdge hit/miss vs origin; purge API works end-to-end; s-maxage overrides max-age
Reverse proxyVCL purge (varnish-test-vtc-syntax); grace-mode behaviour
ApplicationCache-aside write-then-invalidate; key collisions per cache-key-collision-detector
Data storeReplication lag (separate concern; out of scope here)

Anti-patterns

Anti-patternWhy it failsFix
Cache-Control: public on per-user dataShared cache leaks dataUse private for user-specific
Missing Vary: AuthorizationCross-tenant leakAdd to Vary or set private
s-maxage longer than session lifetimeLogged-out users see another user's dataMatch TTL to security window
TTL but no purgeStale-window = TTL even for urgent updatesImplement purge API + use surrogate keys
ETag generated per-request from now()Defeats the validationStable ETag from content hash
no-cache instead of no-store for sensitive dataBrowser still stores; just revalidatesno-store, no-cache, must-revalidate, private
Browser TTL = CDN TTL = origin TTLMulti-tier amplifies staleness instead of layering itOrigin lowest, CDN longer, browser shortest
Cache-aside without write-then-invalidateReads see pre-write state for TTL windowAlways invalidate on write
Vary: *Disables shared cache entirelyUse specific headers
Single Cache-Control for HTML + JSON + assetsOne-size doesn't fit; HTML often short, assets longPer-route directives

Limitations

  • RFC 9111 governs HTTP caches only. Application-tier caches (Redis) use their own semantics; coherence is application- enforced.
  • Doesn't specify replication. Read replicas, multi-region CDN have their own coherence layer.
  • No global invalidation. Cross-tier purge requires coordination; no built-in protocol.
  • Cache-Control parsing has implementation drift. Some CDNs ignore directives they don't recognise; verify per vendor.

References