Two levels of cache (Redis + Caffeine)
Asked Answered
P

1

6

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
Perjure answered 14/8, 2022 at 17:26 Comment(2)
Take a look at: docs.spring.io/spring-framework/docs/current/javadoc-api/org/… might helpKaif
Did you find a solution for this? I am facing a similar issue and requirements to use both caffeine and redis.Relapse
R
4

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;
    }
}
Relapse answered 22/9, 2022 at 18:46 Comment(1)
Hi, I am getting below error: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'redisCacheInterceptor' defined in file [/Users/nishantvarshney/Files/Repos/interceptor/RedisCacheInterceptor.class]: The 'cacheOperationSources' property is required: If there are no cacheable methods, then don't use a cache aspect.Jerusalem

© 2022 - 2024 — McMap. All rights reserved.