Backend Architecture

Enterprise Redis Caching: Cache-Aside, Eviction Policies, and Real-World Optimization

Master advanced distributed caching strategies in Node.js and Java Spring Boot to eliminate database hotspots and prevent cache stampedes.

In enterprise platforms such as HRIS, ERP, and asset tracking systems, database read replicas often become the primary bottleneck during shift transitions and concurrent report generation. Introducing a distributed cache like Redis is the industry standard solution, but naive implementations frequently fall prey to cache stampedes, stale data discrepancies, and memory exhaustion.

In this guide, we walk through production-grade caching patterns that ensure high availability and sub-millisecond retrieval.


1. The Cache-Aside Pattern with Probabilistic Early Expiration

The standard Cache-Aside pattern operates on a simple premise:

  1. Check Redis for the key.
  2. If present (Cache Hit), return cached value.
  3. If absent (Cache Miss), query database, store result in Redis with a TTL, and return.

The Problem: Cache Stampede (Dog-piling)


When a high-traffic key expires, hundreds of simultaneous requests experience a cache miss at the exact same millisecond. All of these concurrent threads then hammer the underlying database simultaneously with the exact same query, causing sudden CPU spikes or database deadlocks.

The Solution: Distributed Locks or XFetch Algorithm

In Java Spring Boot, you can combine Redisson distributed locks or implement a randomized jitter on TTLs:
public DepartmentSummaryDto getDepartmentSummary(String deptId) {
    String cacheKey = "dept:summary:" + deptId;

// 1. Try fetching from Redis
String cachedJson = redisTemplate.opsForValue().get(cacheKey);
if (cachedJson != null) {
return deserialize(cachedJson, DepartmentSummaryDto.class);
}

// 2. Lock to prevent cache stampede
RLock lock = redissonClient.getLock("lock:" + cacheKey);
try {
if (lock.tryLock(2, 10, TimeUnit.SECONDS)) {
// Double-check cache inside lock
cachedJson = redisTemplate.opsForValue().get(cacheKey);
if (cachedJson != null) {
return deserialize(cachedJson, DepartmentSummaryDto.class);
}

// 3. Query Database
DepartmentSummaryDto data = databaseRepository.findSummaryByDept(deptId);

// 4. Add randomized jitter (e.g., 3600s + 0-300s random)
long ttlSeconds = 3600 + ThreadLocalRandom.current().nextLong(300);
redisTemplate.opsForValue().set(cacheKey, serialize(data), ttlSeconds, TimeUnit.SECONDS);

return data;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}

// Fallback if lock timed out
return databaseRepository.findSummaryByDept(deptId);
}


2. Choosing the Right Redis Eviction Policy

Redis operates strictly in memory. When your database cache grows beyond the allocated maxmemory threshold, Redis will reject new writes unless a proper eviction policy is configured in redis.conf.

Eviction PolicyBest Use CaseOperational Risk
allkeys-lruStandard web application cachesMinimal; automatically purges least recently accessed keys
volatile-lruHybrid setups storing both cache and persistent session stateHigh risk if some cache keys lack a TTL
allkeys-lfuRead-heavy datasets where access frequency matters more than recencyRetains frequent hits over transient bursts
noevictionPure message brokers and queuesOut-of-memory errors on new writes
For 95% of web applications, maxmemory-policy allkeys-lru is the safest and most predictable choice.

3. Cache Invalidation Strategies

Invalidating cache is famously one of computer science's hardest challenges. In enterprise systems, prefer Event-Driven Invalidation using Spring Application Events or Redis Pub/Sub:
  • When an asset or employee profile is updated via a PUT /api/v1/employees/{id} command, immediately publish an EmployeeUpdatedEvent.
  • The listener evicts the specific key: redisTemplate.delete("emp:" + id).
  • Avoid deleting entire wildcards (keys emp:*) in production because the Redis KEYS command is blocking and can freeze the entire Redis server single-threaded event loop. Use SCAN instead if key pattern eviction is mandatory.
```
#Redis#Distributed Systems#Caching#Spring Boot#Node.js
Shashank Dubey

Written by Shashank Dubey

Software Engineer specializing in Java Spring Boot, Node.js, React, distributed microservices, and enterprise cloud systems at National Informatics Centre (NIC), Government of India.

View Full Bio & Credentials →


Related In-Depth Engineering Guides