11

Using Spring's caching abstraction, how can I have a cache refresh an entry asynchronously while still returning the old entry?

I am trying to use Spring's caching abstraction to create a caching system where after a relatively short "soft" timeout, cache entries are made eligible for refresh. Then, when they are queried, the cached value is returned, and an asynchronous update operation is started to refresh the entry. I would also

Guava's cache builder allows me to specify that entries in the cache should be refreshed after a certain amount of time. The reload() method of the cache loader can then be overridden with an asynchronous implementation, allowing the stale cached value to be returned until the new one is retrieved. However, spring caching seems to not use the CacheLoader of an underlying Guava cache

Is it possible to do this kind of asynchronous cache refresh using Spring's caching abstraction?

Edit to clarify: With Guava's CacheBuilder I can use refreshAfterWrite() to get the behaviour I want. e.g. from Guava Caches Explained:

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
   .maximumSize(1000)
   .refreshAfterWrite(1, TimeUnit.MINUTES)
   .build(
       new CacheLoader<Key, Graph>() {
         public Graph load(Key key) { // no checked exception
           return getGraphFromDatabase(key);
         }

         public ListenableFuture<Graph> reload(final Key key, Graph prevGraph) {
           if (neverNeedsRefresh(key)) {
             return Futures.immediateFuture(prevGraph);
           } else {
             // asynchronous!
             ListenableFutureTask<Graph> task = ListenableFutureTask.create(new Callable<Graph>() {
               public Graph call() {
                 return getGraphFromDatabase(key);
               }
             });
             executor.execute(task);
             return task;
           }
         }
       });

However, I cannot see a way to get the behaviour of refreshAfterWrite() using Spring's @Cacheable abstraction.

Luke Atkinson
  • 111
  • 1
  • 2
  • 6
  • how about this way. https://github.com/kimwz/caffeine-cache-annotation – kimwz.kr Jun 24 '16 at 04:03
  • @kimwz.kr That's great. It would be nice if it worked with Spring's caching abstraction, so that it could be swapped in and out by changing the `CacheManager` in Spring's configuration, while still using Spring's `@Cacheable` annotation. However, this does look like it delivers what I was after. Thanks a lot. If you make it an answer, I will accept it. – Luke Atkinson Mar 23 '18 at 15:10

2 Answers2

7

Perhaps you could try something like:

  1. Configure the cache:

    @Configuration
    @EnableCaching
    public class CacheConfig {
    
        @Bean
        public CacheManager cacheManager() {
            SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
    
            GuavaCache chache= new GuavaCache("cacheKey", CacheBuilder.newBuilder().build());
    
            simpleCacheManager.setCaches(Arrays.asList(cacheKey));
    
            return simpleCacheManager;
        }
    }
    
  2. Read the value to be cached suppose a String(I use a @Service as example)

    @Service
    public class MyService{
    
        @Cacheable("cacheKey")
        public String getStringCache() {
            return doSomething();
        }
    
        @CachePut("cacheKey")
        public String refreshStringCache() {
            return doSomething();
        }
        ...
    }
    

    Both getStringCache() and refreshStringCache() invoke the same function in order to retreive the value to be cached. The controller invoke only getStringCache().

  3. Refresh the cache with a scheduled tasks doc

    @Configuration
    @EnableScheduling
    public class ScheduledTasks {
    
        @Autowired
        private MyService myService;
    
        @Scheduled(fixedDelay = 30000)
        public void IaaSStatusRefresh(){
            myService.refreshStringCache();
        }
    }
    

    In this way the scheduled task forces the cache refresh every 30s. Anyone who accessed to getStringCache() will find an updated data into the cache.

Ivar
  • 6,138
  • 12
  • 49
  • 61
Matteo
  • 1,893
  • 2
  • 14
  • 13
  • Is my understanding correct? It seems that this would refresh every entry in the cache every 30 seconds which is not really what I want to happen. After an object has been in the cache for 30 seconds, I want it to become eligible for refresh. Then the next time that object is retrieved from the cache, I want it to trigger the asynchronous update. – Luke Atkinson Jun 25 '15 at 10:55
  • Sorry, you understood correctly. I don't know if Spring provides this functionalities – Matteo Jun 25 '15 at 13:09
0

In one project using Spring Cache abstraction, I did the following things to reach the same goal, but still manages to hide the cache's actual vender, i.e., it should work with whatever cache provider Spring supports(currently Guava, but the application can switch to a clustered cache provider if required).

The core concept is to 'capture' cache usage patterns, and 'replay' those operations in another background threads, probably by a scheduler.

It needs to use reflection and some AOP programming for the 'capture' part if I want to keep the code non-intrusive, and luckily for Spring, Spring AOP provides all the toolsets I needed.

Jerry Tian
  • 3,439
  • 4
  • 27
  • 29