Day 11 of building OrderHub added Redis caching with @Cacheable/@CacheEvict — the same read served ~60× faster from memory. But a naïve cache has two failure modes that only show up under load. Day 12 is about making caching safe.
Failure 1: the thundering herd
If a burst of keys all get the same TTL (say 60s), they all expire at the same instant one TTL later — and every request misses at once and stampedes the database. The fix is expiry jitter: add a small random offset (±10%) to every TTL so expiries spread into a smooth trickle instead of one cliff.
Duration ttlWithJitter(Duration base) {
long ms = base.toMillis(), span = (long)(ms * 0.10);
long delta = ThreadLocalRandom.current().nextLong(-span, span + 1);
return Duration.ofMillis(ms + delta);
}
Failure 2: the dogpile
When one hot key expires, dozens of concurrent requests all miss and all recompute the same value together. Single-flight lets exactly one request recompute while the rest wait for its result. In Spring, the simplest per-node form is one flag:
@Cacheable(cacheNames="order", key="#id", sync=true)
public Order getOrder(Long id) { return repo.findById(id).orElseThrow(); }
sync=true makes one thread load while the others block on the cache. Across nodes you'd use a short Redis SETNX lock before recomputing.
Configurable TTLs per cache
Different data goes stale at different rates. Drive per-cache TTLs from configuration so you can tune them per environment without a redeploy:
app:
cache:
default-ttl: 10m
ttls:
order: 5m
orders: 2m
product: 1h
Evict precisely, never flush
Caching is only safe if writes invalidate the right keys. Evict the specific entry that changed and any list/aggregate that includes it — but avoid flushing the whole cache, which just re-triggers the herd you fixed.
Choose an eviction policy
Redis has finite memory; maxmemory-policy decides what to drop when it fills. allkeys-lru (evict least-recently-used) is a solid default for a general cache; volatile-ttl targets keys nearest expiry. Set it deliberately so memory pressure degrades gracefully instead of erroring.
Then prove it: a Testcontainers Redis test that does a cached read and asserts the key exists with a TTL in the expected jittered band.
Live cache-strategy playground (herd vs jitter, dogpile vs single-flight) + the full Spring Boot walkthrough:
https://dev48v.infy.uk/orderhub.php
Top comments (0)