Cache mutex locks using Spring boot

What’s a cache stampede?

A Cache stampede occurs when multiple requests flood the database after cache invalidation/expiration. A mutex lock ensures only one call fetches data from the database at a time and other requests wait and get the c…


This content originally appeared on DEV Community and was authored by P Myls

What's a cache stampede?

A Cache stampede occurs when multiple requests flood the database after cache invalidation/expiration. A mutex lock ensures only one call fetches data from the database at a time and other requests wait and get the cached result, preventing database overload.

Spring Boot’s @Cacheable works out of the box, but you have to override it to support mutex locks to prevent cache stampede.
To override it, we need to extend CacheInterceptor and implement custom locking logic.

Customize your cache manager to use mutex

package com.myapp.config;

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class MutexCacheInterceptor extends CacheInterceptor {
    private final RedisTemplate<String, Object> redisTemplate;

    public MutexCacheInterceptor(CacheManager cacheManager, RedisTemplate<String, Object> redisTemplate) {
        super();
        setCacheManager(cacheManager);
        this.redisTemplate = redisTemplate;
    }

    @Override
    protected Object invokeOperation(CacheOperationInvocationContext<?> context) {
        String cacheName = context.getOperation().getCacheNames().iterator().next();
        String key = context.getKeyGenerator().generate(context.getTarget(), context.getMethod(), context.getArgs()).toString();

        Cache cache = getCacheManager().getCache(cacheName);
        if (cache == null) {
            return super.invokeOperation(context);
        }


        Cache.ValueWrapper valueWrapper = cache.get(key);
        if (valueWrapper != null) {
            return valueWrapper.get();
        }

        String lockKey = "lock:" + key;


        Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 5, TimeUnit.SECONDS);

        if (Boolean.TRUE.equals(lockAcquired)) {
            try {         
                Object result = super.invokeOperation(context);
               cache.put(key, result);
                return result;
            } finally {

                redisTemplate.delete(lockKey);
            }
        } else {
            while (cache.get(key) == null) {
                try {
                    Thread.sleep(100); 
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }

            return cache.get(key).get();
        }
    }
}

Spring config

package com.myapp.config;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheInterceptor cacheInterceptor(CacheManager cacheManager, RedisTemplate<String, Object> redisTemplate) {
        return new MutexCacheInterceptor(cacheManager, redisTemplate);
    }

@Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofHours(1)) // Set cache expiration time
            .disableCachingNullValues();

        return RedisCacheManager.builder(redisConnectionFactory)
            .cacheDefaults(cacheConfig)
            .build();
    }
}

Cache invalidation

package com.myapp.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class CacheService {

    @CacheEvict(value = "users", key = "#userId") 
    public void invalidateCache(String userId) {
        log.info("Cache invalidated for userId: " + userId);
    }
}


This content originally appeared on DEV Community and was authored by P Myls


Print Share Comment Cite Upload Translate Updates
APA

P Myls | Sciencx (2025-03-17T00:09:37+00:00) Cache mutex locks using Spring boot. Retrieved from https://www.scien.cx/2025/03/17/cache-mutex-locks-using-spring-boot/

MLA
" » Cache mutex locks using Spring boot." P Myls | Sciencx - Monday March 17, 2025, https://www.scien.cx/2025/03/17/cache-mutex-locks-using-spring-boot/
HARVARD
P Myls | Sciencx Monday March 17, 2025 » Cache mutex locks using Spring boot., viewed ,<https://www.scien.cx/2025/03/17/cache-mutex-locks-using-spring-boot/>
VANCOUVER
P Myls | Sciencx - » Cache mutex locks using Spring boot. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/03/17/cache-mutex-locks-using-spring-boot/
CHICAGO
" » Cache mutex locks using Spring boot." P Myls | Sciencx - Accessed . https://www.scien.cx/2025/03/17/cache-mutex-locks-using-spring-boot/
IEEE
" » Cache mutex locks using Spring boot." P Myls | Sciencx [Online]. Available: https://www.scien.cx/2025/03/17/cache-mutex-locks-using-spring-boot/. [Accessed: ]
rf:citation
» Cache mutex locks using Spring boot | P Myls | Sciencx | https://www.scien.cx/2025/03/17/cache-mutex-locks-using-spring-boot/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.