Caching
Caching Fundamentals
A cache trades memory for latency by keeping a fast, small copy of data in front of something slow and large — and it only helps if the hit ratio is high enough to matter.
- A cache is only valuable when access has locality (temporal: same key soon again; spatial: nearby keys together) — caching purely random access helps nothing
- Cache-aside (lazy loading): the application checks the cache first, and on a miss reads the source of truth and populates the cache itself — the most common pattern
- Read-through: the cache is responsible for loading on a miss, transparent to the application — simpler call sites, less flexible
- Eviction policy decides what gets thrown out when the cache is full: LRU (evict least-recently-used), LFU (evict least-frequently-used), or TTL (evict by age) — the right one depends on the access pattern
- Caches exist at every layer of a system simultaneously: browser, CDN, reverse proxy, application (Redis/Memcached), and the database's own buffer pool
| Policy | Evicts | Good fit |
|---|---|---|
| LRU | Least recently accessed item | General-purpose — recency predicts reuse |
| LFU | Least frequently accessed item | Stable popularity distributions (some keys always hot) |
| TTL | Items older than a fixed age | Data with a natural freshness window (quotes, weather) |
Optional<Profile> getProfile(String userId) {
Profile cached = cache.get(userId);
if (cached != null) return Optional.of(cached);
Optional<Profile> loaded = database.findProfile(userId);
loaded.ifPresent(p -> cache.put(userId, p, Duration.ofMinutes(10)));
return loaded;
}