What Are You Delegating?

Every abstraction is a trade: it removes work by making decisions for you. Those decisions are still being made — just not by you, and not visibly. For each abstraction: what it genuinely handles, what remains yours, and the escape hatch.

What are you delegating to a cache?
Architecture
You write
1const product = await cache.wrap(`product:${id}`, () => db.products.find(id), { ttl: 300 })
The abstraction handles
  • Sub-millisecond reads for hot keys
  • Absorbing repeated reads so the database sees a fraction of the traffic
  • Eviction when memory fills
  • TTL expiry
Still your responsibility
  • Invalidation — the cache cannot know a row changed unless you tell it, or wait for the TTL
  • Staleness — every cached read is a decision to tolerate an old value for up to ttl seconds
  • Stampedes — keys that expire together recompute together; jitter and single-flight are yours
  • Hot keys and cold starts — one key can pin one node; a restart sends everything to the database at once
  • What happens when the cache is down — cache-aside falls back to the database, which may not survive the fallback
Know your escape hatch

When: Users see stale data, or the database falls over when the cache restarts.

Drop to: An explicit invalidation rule (delete-on-write or events), TTL jitter, single-flight recompute, and a measured hit rate.

A cache buys latency and charges invalidation. The bill always arrives.

Go deeper on this one: