Project: Caching Patterns

Introduction

Caching is the most common Redis use case, but naive caching creates problems at scale. This chapter walks through three classic failure modes—cache penetration, cache breakdown, and cache avalanche—and implements proven defenses. You will build a Spring Boot service that caches product details safely, using real techniques such as null-value caching, mutual exclusion locks, and probabilistic early expiration.

Prerequisites

  • Completion of Spring Boot Integration
  • A running Redis server and a Spring Boot project with spring-boot-starter-data-redis
  • Basic understanding of cache hit and miss concepts

The Three Cache Failure Modes

Cache Penetration

A request asks for data that does not exist in the database. Without protection, every request passes through Redis and hammers the database.

Defense: Cache the absence. Store a short-lived placeholder (for example, an empty JSON object or the string "__NULL__") so repeated requests for the same missing key stop at Redis.

Cache Breakdown

A single hot key expires. Thousands of concurrent requests race to the database to regenerate it, overwhelming that one query.

Defense: Use a mutex. Only the first requester rebuilds the value; others wait or retry from Redis.

Cache Avalanche

Many keys expire at the same instant (for example, after a bulk import with identical TTLs). The database faces a thundering herd of regeneration queries.

Defense: Add jitter. Randomize expiration times so keys do not vanish simultaneously.

Building a Defensive Cache Service

The Product Model

java
import java.io.Serializable;
import java.math.BigDecimal;
 
public record Product(
    Long id,
    String name,
    String description,
    BigDecimal price,
    Integer stock
) implements Serializable {
}

The Repository

java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
 
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
}

The Cache Service with All Defenses

The Controller

Cache-Aside vs Write-Through vs Write-Behind

The implementation above uses cache-aside: the application checks Redis first, falls back to the database, and populates Redis itself.

PatternWhen Redis Is UpdatedComplexityConsistency
Cache-asideOn read miss or explicit invalidationLowEventual
Write-throughOn every database writeMediumStrong
Write-behindAsynchronously after database writeHighEventual, risk of loss

Cache-aside is the most common pattern because it is simple and resilient. Write-through is useful when stale reads are unacceptable, but it adds latency to every write.

Testing the Defenses

Penetration Test

bash
# Request a non-existent product 1000 times
for i in {1..1000}; do
  curl -s http://localhost:8080/products/99999 > /dev/null
done

With null-value caching, only the first request hits the database. The remaining 999 are blocked by the __NULL__ placeholder in Redis.

Breakdown Test

Use a load-testing tool such as wrk or ab against a single product key. Allow the key to expire while traffic is running. The mutex ensures that only one request regenerates the value; the rest wait briefly and then read from the refreshed cache.

FAQ

How long should I cache null values?

Short—typically 1 to 5 minutes. You want to block the thundering herd without permanently hiding newly created data.

What if the mutex holder crashes before releasing the lock?

The setIfAbsent call in the example includes a 10-second expiration. Even if the JVM crashes, Redis removes the lock automatically. Tune this timeout to be longer than your slowest database query.

Can I use Spring Cache annotations instead of manual caching?

Yes, for simple cases. However, Spring Cache does not natively handle null-value caching, mutex-based breakdown protection, or TTL jitter. For high-traffic endpoints, the manual approach shown here gives you full control.

Should I use a Bloom filter instead of null-value caching?

Bloom filters are excellent for penetration prevention when the universe of invalid keys is enormous (for example, user-uploaded content IDs). For a typical e-commerce catalog, null-value caching is simpler and sufficient. If your database has hundreds of millions of products and bots probe random IDs constantly, consider adding a Bloom filter as a first line of defense.

Redis with MongoDB instead of MySQL?

Same cache-aside pattern: MongoDB remains source of truth; Redis holds hot documents or API responses. See MongoDB + Redis deploy and MySQL vs MongoDB for when each store fits.