Skip to content

Cache Versioning#

Instead of invalidating a key, change the key itself. Old keys expire naturally via TTL. New keys start fresh. No explicit invalidation needed.


How it works#

Rather than updating or deleting an existing cache key, you create a new key with a new version embedded in it. Old keys are simply ignored — they expire on their own.

Before update:
  cache key: "user:123:profile:v1"   ← current version pointer
  data: { name: "Alice", bio: "old bio" }

User updates profile:
  → write to DB
  → write "user:123:profile:v2" with new data
  → update "user:123:current-version" = "v2"

Next read:
  → fetch "user:123:current-version" → "v2"
  → fetch "user:123:profile:v2" → new data ✓

Old key:
  → "user:123:profile:v1" still exists but nobody reads it
  → expires quietly via TTL

No invalidation call. No delete. The old key just ages out.


Where cache versioning shines — CDN static assets#

This pattern is most powerful for CDN-hosted static files. Pushing invalidation to CDN edge servers is slow, expensive, and unreliable:

Traditional approach (pushing invalidation):
  Deploy new JS bundle
  → send invalidation request to every CDN edge node worldwide
  → propagates in minutes (not seconds)
  → some edge nodes may be temporarily unreachable → inconsistent
  → CDN charges per invalidation request → expensive at scale

Cache versioning sidesteps all of this:

Versioned URL approach:
  Old bundle: /static/app.js        → serves old code
  New bundle: /static/app.a3f9c2.js → brand new URL, new cache entry everywhere

  → new URL has never been seen by any CDN edge → no stale data to invalidate
  → old URL expires naturally over time
  → zero propagation delay, zero invalidation cost

This is why production JS bundles look like app.a3f9c2.js — the content hash in the filename IS the version. Change one byte of code, you get a new hash, a new filename, a new cache entry everywhere.


The hidden cost for DB-backed data — double lookups#

For CDN assets, versioning is essentially free. For DB-backed data, it adds a lookup cost:

Normal cache read:
  → fetch "user:123:profile" → done (1 cache operation)

Versioned cache read:
  → fetch "user:123:current-version" → "v2"     (1st cache operation)
  → fetch "user:123:profile:v2" → data ✓         (2nd cache operation)

Every read now requires two cache round trips instead of one. At 10 million reads/day, that's 20 million cache operations. At high scale, this latency adds up.

Cache versioning for DB-backed data doubles your cache round trips

For static assets on a CDN — ideal, use it everywhere. For DB-backed data — event-driven or write-through is simpler and cheaper. The double lookup adds unnecessary overhead unless you have a specific reason for versioning.


When to use#

✓ CDN static assets (JS, CSS, images) — the canonical use case
✓ Config or feature flag data with infrequent updates
✓ Any data where you want an audit trail of versions

✗ DB-backed data read millions of times per day — double lookup too costly
✗ Frequently updated data — version numbers accumulate rapidly