Combining Redis and Caffeine for Multi‑Level Caching: Strategies and Best Practices
Combining Redis’s distributed, high‑capacity cache with Caffeine’s ultra‑fast local cache creates a two‑level strategy where most reads hit the in‑process cache, fall‑backs use Redis, and only rare misses query the database, dramatically cutting latency, boosting throughput, and easing DB load.
Cache latency and overload are common pain points for backend engineers. Redis provides a distributed, large‑capacity cache but suffers from network latency, bandwidth pressure and cluster bottlenecks under hot‑spot traffic. Caffeine, a local in‑process cache, offers nanosecond access speed but has limited size and loses data on process restart.
By pairing Redis (second‑level) with Caffeine (first‑level), most requests can be served locally, while less‑frequent data falls back to Redis, and only a small fraction reaches the database. This three‑tier model dramatically reduces response time and database load.
Typical problems
Redis latency: even 1 ms adds up under millions of QPS.
Bandwidth pressure from large objects and hot keys.
Cluster sharding issues.
Caffeine capacity limits, data inconsistency, and process isolation.
Cache‑aside workflow (pseudo code)
public Object get(String key) {
// check local cache first
Object value = caffeineCache.get(key);
if (value != null) {
return value;
}
// fall back to Redis
value = redisTemplate.get(key);
if (value != null) {
// warm up local cache
caffeineCache.put(key, value);
} else {
// finally query the DB
value = database.query(key);
if (value != null) {
redisTemplate.set(key, value);
caffeineCache.put(key, value);
}
}
return value;
}When the data is read‑heavy and write‑light (e.g., product detail pages), updating the local cache after a Redis hit is safe. For write‑heavy scenarios (order status), consider write‑behind or explicit cache invalidation.
Data‑sync patterns
Cache‑aside : read → Caffeine → Redis → DB; write → DB then delete both caches (order matters).
Write‑behind : async queue updates Redis/Caffeine after DB commit (suitable for low‑consistency data).
Pub/Sub : use Redis channels to broadcast invalidation events to all service instances.
Eviction strategies (Caffeine)
LRU – evict least recently used.
LFU – evict least frequently used.
TTL – time‑to‑live expiration.
In practice, a combination of LRU + TTL gives the highest hit rate while keeping memory usage low.
Performance test (4 CPU, 8 GB, 1 Gbps)
Metric
Single Redis
Redis + Caffeine
Improvement
Avg. latency
12 ms
2 ms
83 %
Throughput
8 000 req/s
45 000 req/s
462 %
DB pressure
High
Very low
-
The two‑level cache cuts response time to one‑sixth, raises throughput by more than four times, and almost eliminates database load.
Configuration highlights
Caffeine builder example (Java):
Caffeine.newBuilder()
.maximumSize(10_000) // adjust to ~¼ of heap
.expireAfterAccess(10, TimeUnit.MINUTES)
.expireAfterWrite(5, TimeUnit.MINUTES)
.initialCapacity(2_000)
.concurrencyLevel(Runtime.getRuntime().availableProcessors())
.recordStats()
.build();Redis tips: use Lettuce connection pool, switch to GenericJackson2JsonRedisSerializer, monitor keyspace_hits/keyspace_misses and set alerts when hit rate < 90 %.
Typical scenarios
E‑commerce flash‑sale inventory (hot product stock).
News feed personalization.
Financial risk control with recent transaction windows.
Real‑time log aggregation (PV/UV counters).
Overall, Redis + Caffeine acts like a programmer’s left and right hand: Caffeine handles ultra‑fast hot data, Redis provides durable, larger‑scale storage, and the database remains the source of truth.
Java Tech Enthusiast
Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.