cache-coherence-patterns-reference · git:20260813.2e5e33b · 2026-08-13 · sha256 5a5b3648dbad3b91
cache-coherence-patterns-reference git:20260813.2e5e33bA
Immutable. This exact content never changes and is served at /api/v1/blob/5a5b3648dbad3b91.
--- name: cache-coherence-patterns-reference description: "Pure-reference catalog of cache-coherence patterns across the request path. Defines the five-tier cache stack (browser → CDN → reverse-proxy → application → data store), the per-tier cache-writing patterns (cache-aside, write-through, write-back, write-around, refresh-ahead), and the canonical invalidation strategies (TTL-only, event-driven purge, surrogate keys, version-tagged URLs, soft purge), plus an anti-pattern table and a worked multi-tenant coherence-test example. Deep detail lives in references/: RFC 9111 Cache-Control / Vary / ETag directive tables, the cross-tier test surface, cache-stampede (thundering-herd) mitigations incl. the XFetch formula, and RFC 5861 stale-while-revalidate / stale-if-error semantics. Use for pattern selection, Cache-Control header design, coherence audits, stampede-refresh strategy, and SWR/SIE window design; use a cache-key-collision check when the question is whether two concrete requests collide on a key scheme." --- # cache-coherence-patterns-reference ## Overview Keeping cached values consistent with their source of truth across tiers (browser, CDN, reverse-proxy, application, data store). Wrong coherence shows as stale data; wrong invalidation shows as cache stampedes per [references/stampede.md](references/stampede.md). 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. ## How to use this reference 1. **Locate the tier(s)** the value lives in from the five-tier stack - each tier (browser, CDN, reverse proxy, application, data store) has its own TTL and invalidation mechanism. 2. **Choose the write pattern** for the application tier from the cache-writing patterns table (cache-aside, write-through, write-back, write-around, refresh-ahead) based on the read/write mix and how much consistency you need. 3. **Choose the invalidation strategy** from the invalidation strategies table (TTL-only, event-driven purge, surrogate keys, version-tagged URLs, soft purge) based on the staleness window you can tolerate. 4. **Set the contract** - design the `Cache-Control` directives, `Vary` key, and ETag validators per [references/rfc-9111-http-caching-directives.md](references/rfc-9111-http-caching-directives.md). 5. **Write the per-tier coherence test** from the cross-tier problems and test-surface catalog in [references/cross-tier-coherence-and-test-surface.md](references/cross-tier-coherence-and-test-surface.md), then re-check the design against the Anti-patterns table below. ## The five-tier stack | Tier | Where | Common TTL | Invalidation | |---|---|---|---| | Browser | `Cache-Control: private` | minutes-hours | TTL only (or Service Worker code) | | CDN | Cloudflare / Fastly / CloudFront / Akamai | seconds-days | Purge API or surrogate-key tag | | Reverse proxy | Varnish, nginx | seconds-hours | VCL purge / nginx cache_purge | | Application | Redis / Memcached / in-process | seconds-minutes | Direct delete / pub-sub broadcast | | Data store | Postgres query cache, RDS read replicas | seconds | Replication-driven | A coherence bug at any tier surfaces at the user. The test surface is layered; each tier needs its own coherence tests. ## Cache-writing patterns For application-tier caches (Redis): | Pattern | Flow | When | |---|---|---| | **Cache-aside** (lazy load) | Read miss → read source → populate → return; Write → invalidate cache | Read-heavy, eventual consistency OK | | **Write-through** | Write → write source → write cache (synchronous) | Strong consistency, latency tolerable | | **Write-back** | Write → write cache → async write to source | Burst writes; data-loss risk on cache crash | | **Write-around** | Write → write source (skip cache); reads do cache-aside | Write-heavy with rare re-reads | | **Refresh-ahead** | Background refresh before TTL expires | Predictable read patterns; hot keys | ## Invalidation strategies | Strategy | Mechanism | Trade-off | |---|---|---| | **TTL-only** | Just let it expire | Simple; possibly-stale window = TTL | | **Event-driven purge** | Source-of-truth update fires a delete | Coupling; firehose at high write rate | | **Surrogate keys** (Fastly, Varnish) | Tag responses; purge by tag | Group-invalidation; coordination cost | | **Version-tagged URLs** | `/api/users?_v=42`; new version = new key | Immutable cache; full deploy per change | | **Soft purge** | Mark stale, keep serving until refresh | Used by stale-while-revalidate per [references/stale-while-revalidate.md](references/stale-while-revalidate.md) | ## Worked example: a multi-tenant dashboard endpoint Scenario: `/api/users` serves per-tenant dashboard data, is read-heavy, and must never leak one tenant's rows to another. Walk the four decisions from **How to use this reference**, then the test. 1. **Tiers.** The response flows browser → CDN → application (Redis) → data store. Because the payload is per-user, it must not sit in a shared cache: the browser tier gets `Cache-Control: private` and the CDN is bypassed for this route (or keyed per tenant), not left on the shared edge. 2. **Write pattern.** Reads dominate and eventual consistency after a profile edit is acceptable, so the application tier uses **cache-aside**: a read miss loads from the data store and populates Redis; a write invalidates the tenant's Redis key. 3. **Invalidation.** A profile edit is an urgent update, so TTL-only is not enough - pair the TTL with **event-driven purge** so the write fires a delete of the tenant's cache key immediately. 4. **Contract.** Set `Vary: Authorization` so each tenant gets a separate cache entry (the fix for the cross-tenant leak in the Anti-patterns table), and add a content-hash `ETag` so an unchanged reload returns `304 Not Modified` instead of the full body. Coherence test (the browser-tier "write → reload → see old" case): - **Arrange:** tenant A loads `/api/users`; the response is cached. - **Act:** tenant A edits a user - which must fire the Redis purge - then reloads the page. - **Assert (staleness):** the reload shows the edited value, not the pre-write state. A failure here means the write path skipped the invalidate - the "cache-aside without write-then-invalidate" anti-pattern. - **Assert (isolation):** a request from tenant B with a different `Authorization` header never returns tenant A's cached rows, proving the `Vary: Authorization` split holds. ## Anti-patterns | Anti-pattern | Why it fails | Fix | |---|---|---| | `Cache-Control: public` on per-user data | Shared cache leaks data | Use `private` for user-specific | | Missing `Vary: Authorization` | Cross-tenant leak | Add to Vary or set `private` | | `s-maxage` longer than session lifetime | Logged-out users see another user's data | Match TTL to security window | | TTL but no purge | Stale-window = TTL even for urgent updates | Implement purge API + use surrogate keys | | ETag generated per-request from `now()` | Defeats the validation | Stable ETag from content hash | | `no-cache` instead of `no-store` for sensitive data | Browser still stores; just revalidates | `no-store, no-cache, must-revalidate, private` | | Browser TTL = CDN TTL = origin TTL | Multi-tier amplifies staleness instead of layering it | Origin lowest, CDN longer, browser shortest | | Cache-aside without write-then-invalidate | Reads see pre-write state for TTL window | Always invalidate on write | | `Vary: *` | Disables shared cache entirely | Use specific headers | | Single Cache-Control for HTML + JSON + assets | One-size doesn't fit; HTML often short, assets long | Per-route directives | ## Deep references The contract layer and the audit-and-test surface live in companion references so this file stays a decision surface: - **RFC 9111 directive tables** - `Cache-Control` response directives, `Vary` key derivation, and `ETag` / `If-None-Match` revalidation: [references/rfc-9111-http-caching-directives.md](references/rfc-9111-http-caching-directives.md). - **Cross-tier coherence problems + per-tier test surface** - the seam bugs to audit for and what to test at each tier: [references/cross-tier-coherence-and-test-surface.md](references/cross-tier-coherence-and-test-surface.md). - **Cache stampede (thundering herd)** - symptoms, the three mitigation families, the XFetch formula, and stampede load tests: [references/stampede.md](references/stampede.md) (implementations in [references/stampede-mitigations.md](references/stampede-mitigations.md)). - **stale-while-revalidate / stale-if-error (RFC 5861)** - lifecycle, vendor support, and SWR/SIE window tests: [references/stale-while-revalidate.md](references/stale-while-revalidate.md). ## 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 - RFC 9111 HTTP Caching: [www.rfc-editor.org/rfc/rfc9111.html](https://www.rfc-editor.org/rfc/rfc9111.html); full directive tables in [references/rfc-9111-http-caching-directives.md](references/rfc-9111-http-caching-directives.md). - RFC 8246 `immutable`: [www.rfc-editor.org/rfc/rfc8246.html](https://www.rfc-editor.org/rfc/rfc8246.html). - Cross-tier coherence problems + per-tier test surface (with their cross-references): [references/cross-tier-coherence-and-test-surface.md](references/cross-tier-coherence-and-test-surface.md). - RFC 5861 stale-while-revalidate / stale-if-error: [references/stale-while-revalidate.md](references/stale-while-revalidate.md). - Cache stampede: [references/stampede.md](references/stampede.md). - Cross-tenant leaks via cache: `cross-tenant-data-leak-tests`. - Consumed by: `redis-cache-tests`, `cdn-cache-purge-tests`, `varnish-test-vtc-syntax`.