Cache Avalanche
Most caches expire at the same time.
Solution:
- Use random ttl.
// use ttl function instead of fixed duration.
cacheConfiguration.entryTtl((k, v) ->
Duration.ofSeconds(seconds + ThreadLocalRandom.current().nextInt(seconds / 2))
);
Cache Breakdown
Hotkey caches expire, causing lots of requests to hit the database.
Solution:
- Use a lock to query data from the database.
// use sync just for single jvm.
// use distributed lock for cluster.
@Cacheable(cacheNames = "product", key = "#id", sync = true)
public Product getProduct(Long id) {
return productRepository.findById(id);
}
- Hot key caches never expire, refresh them async.
Cache Penetration
The key does not exist in either cache or database.
Solution:
Validate request parameters.
Cache null values.
spring: cache: redis: cache-null-values: trueOr enable in code.
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().cacheNullValues();Use a Bloom Filter (guava) to filter out non-existent keys.
@Component
public class UserBloomFilter {
private final BloomFilter<Long> bloomFilter =
BloomFilter.create(Funnels.longFunnel(), 1_000_000, 0.01);
@PostConstruct
public void init() {
userRepository.findAllIds().forEach(bloomFilter::put);
}
public boolean mightContain(Long id) {
return bloomFilter.mightContain(id);
}
}
public User getUser(Long id) {
if (!bloomFilter.mightContain(id)) {
return null;
}
return userRepository.findById(id).orElse(null);
}