Real-world combination
Cache-Aside Layer
A cache sitting in front of your store, behind the same interface — hits return instantly, misses load once and populate the cache for next time.
Every read hits the slow store
A handful of "hot" keys get read constantly — but each read still pays the full cost of a database query or a remote API call, even though the answer barely ever changes.
function getUser(id) {
// hits the DB every single time —
// even for the same 5 hot ids, 1000×/sec
return db.query('SELECT * FROM users WHERE id=?', id);
}
Front the store with a same-interface cache
A Proxy/Decorator cache sits transparently in front of the store. Read path: check cache → HIT returns fast; MISS loads from the store, populates the cache, then returns. An eviction Strategy (LRU / TTL) bounds memory; writes invalidate.
class CacheAside {
get(k) {
let v = cache.get(k);
if (v !== undefined) return v; // HIT
v = store.load(k); // MISS → slow
cache.set(k, v, { ttl }); // populate
return v;
}
}
Read some keys — watch hits, misses and eviction
Cache holds max 3 keys (LRU-ordered). First read of a key is a MISS (goes to the store, slower); reading it again is a HIT (instant). Read a 4th key and the least-recently-used entry is evicted. Tick the clock to expire entries past their TTL.
Read a key to begin.
Lazy, bounded, but never free
- Cache-aside (lazy) reads: the cache is populated on demand, on the first miss — not upfront.
- Huge latency win for hot data: repeat reads become O(cache) instead of O(store).
- Bound it: LRU caps memory by size, TTL caps how long an entry may be served.
- Invalidate on write — update or delete the cached entry, or the client will read stale data.
- Caution: one of the two genuinely hard problems in computer science — cache invalidation — plus a cold cache means a "thundering herd" of misses right after a restart.
- Real uses: Redis / Memcached sitting in front of Postgres, MySQL, S3, or a slow upstream API.