Implementing Caching Logic
1. Implementing Cache-Aside Pattern
Example: Read-through
public User get(long id) {
var cached = cache.get(id);
if (cached != null) return cached;
var user = repo.findById(id).orElseThrow();
cache.put(id, user);
return user;
}
2. Implementing Write-Through Cache
| Aspect | Detail |
|---|---|
| Write | To cache and DB synchronously |
| Read | From cache (always fresh) |
| Pro | Strong consistency |
| Con | Write latency higher |
3. Implementing Write-Behind Cache
| Aspect | Detail |
|---|---|
| Write | To cache; async to DB |
| Pro | Low write latency, batching |
| Con | Data loss risk on crash |
| Use | Metrics, analytics, non-critical |
4. Implementing Cache Invalidation Logic
| Strategy | Detail |
|---|---|
| TTL | Time-based; eventual consistency |
| Explicit on write | Evict matching keys |
| Event-driven | Invalidate on domain event |
| Versioned key | Bump version on data change |
Warning: Cache invalidation is one of the two hard problems in CS — design for staleness tolerance.
5. Implementing In-Memory Caching
Example: Caffeine
Cache<Long, User> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofMinutes(10))
.recordStats()
.build();
User u = cache.get(id, repo::findById);
6. Implementing Distributed Caching
| System | Trait |
|---|---|
| Redis | Single-master + replicas; rich types |
| Memcached | Simple key/value; multi-threaded |
| Hazelcast | Embedded, JVM |
| Apache Ignite | Compute + cache |
7. Implementing Cache Hierarchies
Request → L1 (in-memory, <1ms) → miss → L2 (Redis, ~1-5ms) → miss → DB
↑ ↓
backfill on miss ← - - - - - - - - - ┘
8. Implementing Cache Warming
| Trigger | Detail |
|---|---|
| App startup | Load hot keys |
| Scheduled job | Refresh popular content |
| Event-driven | Pre-load on related event |
| Predictive | ML-based prefetch |
9. Designing Cache Key Generation
| Practice | Detail |
|---|---|
| Namespace prefix | user:123 |
| Include version | v2:user:123 for migrations |
| Tenant scope | tenant:42:user:123 |
| Avoid PII | Hash sensitive parts |
| Hash long keys | SHA-256 if > 250 chars |
10. Handling Cache Stampede
| Technique | Detail |
|---|---|
| Single-flight | Coalesce concurrent loads (Caffeine does this) |
| Probabilistic early expire | Refresh slightly before TTL |
| Mutex/lock per key | Only one loader allowed |
| Stale-while-revalidate | Serve stale, refresh async |
11. Using Cache Annotations
Example: Spring Cache
@Cacheable(value = "users", key = "#id")
public User get(long id) { return repo.findById(id).orElseThrow(); }
@CachePut(value = "users", key = "#u.id")
public User update(User u) { return repo.save(u); }
@CacheEvict(value = "users", key = "#id")
public void delete(long id) { repo.deleteById(id); }
12. Implementing Cache Metrics
| Metric | What to Track |
|---|---|
| Hit ratio | hits / (hits + misses) |
| Eviction rate | Capacity pressure |
| Load time p99 | Slow miss path |
| Size | Memory usage |
| Stampede events | Concurrent loads coalesced |