Testland
Browse all skills & agents

stale-while-revalidate-reference

Pure-reference catalog of RFC 5861's stale-while-revalidate + stale-if-error Cache-Control extensions. Defines stale-while-revalidate=N (caches MAY serve a stale response while asynchronously revalidating, up to N seconds after expiry) and stale-if-error=N (caches MAY serve stale on 5xx upstream errors). Distinguishes from RFC 9111's must-revalidate (forbids serving stale) and from manual cache-aside refresh (synchronous). Covers the interaction with the freshness lifetime (max-age) and the cache-stampede-mitigation properties. Use when designing the cache-refresh boundary or auditing existing Cache-Control headers.

Install with skills.sh (any agent)

npx skills add testland/qa --skill stale-while-revalidate-reference
View source

stale-while-revalidate-reference

Overview

stale-while-revalidate (SWR) and stale-if-error (SIE) are Cache-Control extensions defined in RFC 5861 (opens in new window). RFC 9111 references them as informative; they are widely implemented by browsers, CDNs (Cloudflare, Fastly, CloudFront), and reverse proxies (Varnish via grace mode).

Both directives extend the cache lifetime beyond freshness, but in different ways:

DirectiveWhen stale-serve happensWhat revalidation looks like
stale-while-revalidate=NUp to N seconds after max-age expiresBackground async; client sees stale
stale-if-error=NWhen origin returns 5xx, up to N seconds after max-ageSynchronous on error; client sees stale instead of 5xx

When to use

  • Designing the cache-refresh model for an endpoint that values availability + latency over freshness.
  • Mitigating cache stampedes per cache-stampede-reference: background revalidation = no thundering herd.
  • Improving availability under partial origin outage.
  • PR review of Cache-Control changes.

stale-while-revalidate

Per RFC 5861 §3 (opens in new window):

"When present in a response, caches MAY serve the response in which it appears after it becomes stale, up to the indicated number of seconds."

Syntax: Cache-Control: max-age=60, stale-while-revalidate=300.

Lifecycle

  1. t < max-age → cache hits, response served from cache.
  2. max-age < t < max-age + stale-while-revalidate → cache hits with stale response, and cache fires async revalidation to origin.
  3. Async revalidation succeeds → cache refreshed.
  4. t > max-age + stale-while-revalidate → "truly stale"; subsequent request blocks on origin.

The client always sees a response in steps 1-3. Stampedes are eliminated in step 2 - only the first request triggers revalidation; others coast on the stale value.

When the async revalidation fails

The cache may keep serving stale until the SWR window expires. Vendors differ here:

  • Cloudflare: keeps serving stale until SWR window expires, then enters origin-block mode.
  • Fastly: more aggressive - surfaces 5xx after one failed revalidation.
  • Varnish (via grace): configurable per VCL.

Test the actual behaviour per vendor.

stale-if-error

Per RFC 5861 §4 (opens in new window):

"A cached stale response MAY be used to satisfy the request, regardless of other freshness information, provided staleness hasn't exceeded the specified limit."

Applies to status codes 500, 502, 503, 504.

Syntax: Cache-Control: max-age=60, stale-if-error=86400.

Pattern: 1-minute freshness, but 1-day grace if origin is down.

Composition with stale-while-revalidate

Cache-Control: max-age=60, stale-while-revalidate=300, stale-if-error=86400

Interpretation:

TimeWhat happens
0-60sCache hit, fresh
60-360sCache hit, stale; async revalidate
60-86400s and origin returns 5xxCache serves stale (last good value)
360s+ if revalidation succeedsNormally back to fresh after revalidation
86400s + origin downTruly stale + error

Interaction with must-revalidate

Per RFC 9111: must-revalidate "Once the response has become stale, a cache MUST NOT reuse that response to satisfy another request until it has been successfully validated."

must-revalidate and stale-while-revalidate are mutually exclusive in spirit. Setting both is undefined behaviour by RFC 9111 + RFC 5861 reading; most caches honour the strictest (must-revalidate wins).

For SWR / SIE to work, don't add must-revalidate.

Per-vendor support

CacheSWR supportSIE supportCaveat
CloudflareYes (since 2018)YesHonours both response and request directives
FastlyYes (via Surrogate-Control or Cache-Control)YesStale-on-error more aggressive
CloudFrontYes (since 2022)YesStale-on-error needs origin error caching policy
VarnishYes (grace keyword in VCL)Yes (stale-if-error)See varnish-test-vtc-syntax
nginxYes (proxy_cache_use_stale updating)Yes (proxy_cache_use_stale error timeout)Different config keyword
Chrome/FirefoxYes (browser cache honours SWR/SIE)YesPer-tab behaviour may surprise; test
Service WorkersManual implementation in coden/aWorkbox provides a SWR strategy

Testable behaviours

BehaviourTest
SWR serves stale within windowSet max-age=1, SWR=300; wait 5s; request → stale served + async revalidate
SWR triggers revalidationVerify origin sees one revalidate request after the user's request returned
SWR window enforcedWait > max-age + SWR; next request blocks origin
SIE serves stale on origin 5xxTake origin down; request within SIE window → 200 with stale data + warning header (RFC 7234 Warning header may be present)
SIE window enforcedOrigin down beyond SIE window → user sees 5xx
SWR + must-revalidate doesn't surface staleVerify must-revalidate wins
Stampede mitigation under loadN=1000 concurrent at t=max-age+1s → origin sees 1-2 revalidates, not 1000

Anti-patterns

Anti-patternWhy it failsFix
Cache-Control: must-revalidate, stale-while-revalidate=300Contradictory; must-revalidate wins, SWR silently ignoredDrop must-revalidate
stale-while-revalidate on private user dataStale for one user could expose old data to that same userBe deliberate; pair with private
SWR=0No grace period; equivalent to omittingUse ≥30s
SWR window > max-age * 10Stale data shown for an excessive fraction of total lifetimeKeep proportionate
SIE without alarm on origin 5xx rate"Site looks fine" but origin down for daysPair SIE with monitoring
Per-page Cache-Control inconsistent (some SWR, some not)Confusing UX during partial outagesCodify SWR policy per response class
Browser ignores SWR (older browsers)Polyfill via Service Worker for critical pathsTest with target browser matrix

Limitations

  • Async revalidation is best-effort. Cache may evict the stale entry before revalidation completes (memory pressure) → fall back to blocking fetch.
  • Doesn't help on first request. SWR requires a previously- cached response; cold cache always blocks.
  • Doesn't apply to no-store. That directive overrides everything.
  • Origin must be ready for the async revalidation traffic. An overloaded origin gets hit even harder by background revalidates from millions of clients.
  • Doesn't propagate the staleness. The user can't tell they're seeing stale data without a Warning header (which some CDNs strip).

References

Related skills

browser-cache-control-tests

Wraps browser-side Cache-Control testing with Playwright (Cypress for legacy stacks): asserting response Cache-Control headers from Network events, ETag round-trips (If-None-Match → 304), service-worker strategies (Workbox cacheFirst / networkFirst / staleWhileRevalidate), and reload semantics (normal vs hard). Covers MDN Cache-Control + RFC 9111. Use when auditing browser-tier caching in E2E tests. For CDN-edge purge use cdn-cache-purge-tests; for the reverse-proxy tier use varnish-test-vtc-syntax; for an app-tier store use redis-cache-tests; SWR directive semantics live in stale-while-revalidate-reference.

cache-coherence-patterns-reference

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 - the RFC 9111 Cache-Control / Vary / ETag directive tables and the cross-tier coherence + per-tier test surface - lives in references/. Use for pattern selection, Cache-Control header design, and coherence audits; use a cache-key-collision check 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, and the cache-key-collision check.

cache-key-discriminator-audit

Audits whether a cache key carries every discriminator the cached response actually depends on, so two requests that must not share a slot cannot collide. Ranks identity discriminators (tenant, user, authorization scope, plan tier) above presentation ones (locale, region, currency, feature flag), maps each missing discriminator to the data-exposure or wrong-content consequence it causes, classifies each key-and-value pair into a critical / high / medium severity band (including the Python lru_cache-on-an-instance-method trap), and writes the fix as a namespaced key builder plus the matching HTTP Vary header. Use when a cache key is being designed or changed, when a per-user or per-tenant response is about to be stored in a shared cache or CDN, or when investigating a report that one user or tenant saw another's data.

cache-stampede-reference

Pure-reference catalog of cache-stampede (thundering-herd) phenomena and mitigations: parallel misses on key expiry trigger simultaneous recomputation (often congestion-collapse), countered by three families - locking, external recomputation near-expiry, and probabilistic early expiration via XFetch (`(time() - delta * beta * log(rand(0,1))) >= expiry`). Use when designing cache-refresh strategy or diagnosing a stampede incident. This is the single failure-mode pattern; for the broader multi-tier pattern catalog use cache-coherence-patterns-reference, for the stale-while-revalidate / stale-if-error extensions use stale-while-revalidate-reference; a reference consumed by redis-cache-tests, not a runnable test.

cdn-cache-purge-tests

Wraps CDN cache-purge testing patterns for Cloudflare (POST /zones/{zone_id}/purge_cache, single-file / everything / cache-tags / hostname / prefix), Fastly (POST purge-by-key / purge-all, surrogate-keys via Surrogate-Key header), and CloudFront (CreateInvalidation API + paths). Covers end-to-end test patterns (write origin → trigger purge → assert edge serves fresh), purge-propagation delay testing (typically 1-30s globally), surrogate-key + cache-tag patterns for group-purge, and Cache-Status header verification (cf-cache-status: HIT/MISS/BYPASS). Use when designing or auditing CDN cache-invalidation workflows.

memcached-tests

Wraps Memcached cache testing against a real container: an inline set/get/expire/no-persistence worked example, with the exhaustive protocol-command tests (set/get/add/cas/incr/decr, TTL 0=never-expire / 30-day Unix-timestamp boundary) and the LRU-eviction, consistent-hashing distribution, ElastiCache Auto Discovery, and CI-wiring deep-dives in references/. Use when writing tests for an application that uses Memcached as its primary cache, when verifying ElastiCache Memcached cluster behaviour, or when contrasting Memcached eviction and distribution semantics against Redis.

redis-cache-tests

Wraps Redis cache testing: EXPIRE / PEXPIRE / TTL verification (Redis 7+ NX/XX/GT/LT flags), cache-aside write-then-invalidate (write source → DEL key → assert fresh read), eviction under memory pressure (maxmemory + allkeys-lru), pub/sub invalidation across nodes, and tenant key-namespacing. Use when Redis is the app's primary cache. For a Memcached app-tier store use memcached-tests; for the browser/HTTP tier use browser-cache-control-tests; a runnable test, not the cache-stampede-reference or cache-coherence-patterns-reference catalogs.

varnish-test-vtc-syntax

Wraps the varnishtest CLI + VTC (Varnish Test Case) syntax for testing VCL configurations. Covers the VTC test-file format (varnishtest scripts with server { ... } + client { ... } + varnish v1 -vcl+backend { ... } blocks), the grace-mode + saint-mode behaviours (stale-while-revalidate + stale-if-error equivalents in VCL), the PURGE method handler pattern (vcl_purge subroutine + ACL guards), and surrogate-key invalidation via xkey vmod. Use when authoring or testing Varnish-based caching layers.