Two levels of cache (Redis + Caffeine)

Viewed 152

When profiling an application it came up that Redis is impacting the execution times because there are many sleeps in threads. I need to implement two levels of cache or think about solution of this problem.

I would like to have two levels of caches:

  • L1 - local for each instance of deployment,
  • L2 - cache global for all instances of same deployment,

The solution that I came up with is:

  • Create two CacheManagers (CaffeineCacheManager and RedisCacheManager),
  • Initialize same caches for each cache manager,
  • Use annotation @Caching with cacheable={} to use two caches,

    @Caching(cacheable = {
            @Cacheable(cacheNames = CacheConfiguration.HELLO_WORLD),
            @Cacheable(cacheNames = CacheConfiguration.HELLO_WORLD, cacheManager = "cacheManagerRedis")
    })
    public String generate(String name)
    {
        log.info("  Cached method call...");
        return helloWorldService.generate(name);
    }

The structure of classes is similar to: CachedService (annotations here) -> NonCachedService

The problem I am facing:

I wanted to have it working in flow (yes - works/n - not working):

  • [ y ] data is fetched and then cached to both caches Redis and local - this works
  • [ y ] if data exists in local cache do not move it to redis - this works
  • [ y ] if any of caches contains the data it will be fetched from cache
  • [ n ] if data exists in Redis, move it to local - this does not work

Modification of @Caching annotation to have put={} where it would put values into local cache is making whole cache not working.


    @Caching(cacheable = {
            @Cacheable(cacheNames = CacheConfiguration.HELLO_WORLD),
            @Cacheable(cacheNames = CacheConfiguration.HELLO_WORLD, cacheManager = "cacheManagerRedis")
    }, put = {
            @CachePut(cacheNames = CacheConfiguration.HELLO_WORLD),
    })
    public String generate(String name)
    {
        log.info("  Cached method call...");
        return helloWorldService.generate(name);
    }

  • Do you know any spring-ready solutions to work with two levels of cache?
  • I've read about local caching with Redis but it does not mean anything similar to my case (it's just the standard redis use case),
  • I am left only with double-layered services structure to achieve this goal? Similar to CachedLocal -> CachedRedis -> NonCached
1 Answers

For anyone else looking for this, I was able to implement the CacheInterceptor suggested by Ankit.

Example:

public class RedisMultiCacheInterceptor extends CacheInterceptor {

@Autowired
private CacheManager caffeineCacheManager;

@Override
protected Cache.ValueWrapper doGet(Cache cache, Object key) {
    //Get item from cache
    var superGetResult = super.doGet(cache, key);

    if (superGetResult == null) {
        return superGetResult;
    }

    //If retrieved from Redis, check if it's missing from caffeine on local and add it
    if (cache.getClass() == RedisCache.class) {
        var caffeineCache = caffeineCacheManager.getCache(cache.getName());

        if (caffeineCache != null) {
            caffeineCache.putIfAbsent(key, superGetResult.get());
        }
    }

    return superGetResult;
}

}

Related