Beyond @Cacheable: 4 Redis Design Patterns That Separate Good Coders from System Architects

The article explains why merely adding @Cacheable is insufficient and introduces four essential Redis design patterns—penetration protection, avalanche mitigation, serialization control, and consistency handling—plus client‑level considerations, each illustrated with concrete code and diagrams.

LuTiao Programming
LuTiao Programming
LuTiao Programming
Beyond @Cacheable: 4 Redis Design Patterns That Separate Good Coders from System Architects

Technical Depth

Adding three @Cacheable annotations reduced database load by 40%, but when Redis memory reached 100% sessions were lost and stale data was returned, highlighting the need for systematic Redis design.

Cache Penetration Protection

Problem: Queries for non‑existent data return null; Spring does not cache null, causing repeated database hits under high concurrency.

Typical wrong code:

@Cacheable("products")
public Product findById(Long id) {
    return productRepository.findById(id).orElse(null);
}

Correct approach – cache empty objects:

@Cacheable(value = "products", unless = "#result == null")
public Product findById(Long id) {
    return productRepository.findById(id).orElseGet(() -> Product.empty());
}

Manual control example:

public Product findById(Long id) {
    String key = "product:" + id;
    Product cache = redisTemplate.opsForValue().get(key);
    if (cache != null) {
        return cache;
    }
    Product db = productRepository.findById(id).orElse(null);
    redisTemplate.opsForValue().set(key,
        db == null ? Product.empty() : db,
        5, TimeUnit.MINUTES);
    return db;
}

Cache Avalanche Mitigation

When many keys expire simultaneously the database can be flooded.

Solution: randomize TTL.

public void cacheWithRandomTTL(String key, Object value) {
    int baseTtl = 3600; // 1 hour
    int random = ThreadLocalRandom.current().nextInt(300); // up to 5 minutes
    redisTemplate.opsForValue().set(key, value, baseTtl + random, TimeUnit.SECONDS);
}

Serialization Strategy

Default JDK serialization produces unreadable keys and consumes extra memory.

Configure RedisTemplate to use JSON serialization:

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer<Object> serializer =
                new Jackson2JsonRedisSerializer<>(Object.class);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }
}

Key: human‑readable for easier troubleshooting.

Value: JSON for cross‑language compatibility and space savings.

Avoid default JDK serialization.

Cache Consistency Strategy

Database updates do not automatically refresh cached entries, leading to stale reads.

Wrong pattern – evicting all entries:

@CacheEvict(value = "products", allEntries = true)
public Product updateProduct(Product product) {
    return productRepository.save(product);
}

Recommended double‑delete pattern:

public void updateProduct(Product product) {
    String key = "product:" + product.getId();
    // First delete
    redisTemplate.delete(key);
    // Update DB
    productRepository.save(product);
    // Delayed second delete to avoid race conditions
    Executors.newSingleThreadScheduledExecutor().schedule(() -> {
        redisTemplate.delete(key);
    }, 500, TimeUnit.MILLISECONDS);
}

Lettuce Client and Connection Management

Spring Boot defaults to Lettuce, a Netty‑based asynchronous client. Its thread model and connection pool affect performance.

Basic configuration:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0

Optimization tips:

Configure the connection pool according to workload.

Avoid blocking calls that block Netty event loops.

Monitor active connections under high concurrency.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

RedisserializationSpringCachingTTLconsistencylettuce
LuTiao Programming
Written by

LuTiao Programming

LuTiao Programming is a friendly community offering free programming lessons. We inspire learners to explore new ideas and technologies and quickly acquire job-ready skills.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.