Caching

Purpose: store frequently-read data closer to the reader and faster than the source, so you serve hot reads without hammering the database. The universal trade-off: speed for freshness.

Where caches live (the layers)

The rule: cache at the layer closest to the user that still gives correct-enough data.

The three patterns

Pattern How Read Write Use when
Cache-aside App checks cache → miss → loads DB → writes cache Lazy App invalidates/updates cache Most systems
Write-through Every write also writes cache Always fresh Slower writes Read-heavy, must be fresh
Write-back Write goes to cache, flushed to DB later Fast Risk of loss if cache dies Tolerates loss, write-heavy

TTL (time-to-live) is the safety valve: every cache entry expires, bounding how stale data can get even if invalidation fails.

The failure modes a senior designs for

Where it fits your systems

Your device-token lookup was the exact case: Redis cache on user_id → tokens, because reading tokens per notification (12k QPS) would hammer MongoDB. The two sharpening rules from that session apply everywhere: invalidate on change, and the cache is a cache — the DB is the truth.

Ask yourself

  1. What's my read:write ratio? (High ratio → cache wins.)
  2. How stale can this data be? (Determines TTL and pattern.)
  3. If the cache vanishes at 9 AM, does my system still work?