3

I'm using Spring cache abstraction with Guava cache. I have a method with @Cacheable annotation and parameter (that serves as a cache key) to put values into the cache. But this method is accessed in a multi threaded env so there are multiple concurrent calls to the method with the same parameter value. So that means the same logic that creates the value to be cached is done for the same cache key multiple times and put into the cache multiple times concurrently. It'd be much more efficient if for each parameter value (cache key) the method would be called only once and put into the cache once. Can Spring handle such a scenario?

mike27
  • 965
  • 3
  • 12
  • 22
  • Is program logic necessary to initialize the cache, or can the cache's entries be configured in the application context? – Keith Sep 01 '15 at 21:16
  • Have you confirmed that this actually happens, or do you just think it works that way? Because Guava caches don't work that way. The value is loaded a single time. http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/LoadingCache.html#get%28K%29: *If another call to get(K) or getUnchecked(K) is currently loading the value for key, simply waits for that thread to finish and returns its loaded value* – JB Nizet Sep 01 '15 at 21:20
  • @JBNizet I think he's concerned about multiple threads calling ``put`` and overwriting the same key/value. I'm wondering if this is just solved by using ``putIfAbsent(...)`` – Keith Sep 01 '15 at 21:23
  • As always, if we had the code we wouldn't have to guess. – JB Nizet Sep 01 '15 at 21:25
  • 2
    Unfortunately both Spring and JSR-107 cache annotations do not atomically compute the value. Instead they race on get-compute-put. This is intentional, though I strongly disagree and consider it faulty reasoning. – Ben Manes Sep 01 '15 at 23:24
  • Spring Cache [interceptor](https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java#L331), Spring JCache [interceptor](https://github.com/spring-projects/spring-framework/blob/f41de12cf62aebf1be9b30be590c12eb2c030853/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResultInterceptor.java#L43), JCache RI [interceptor](https://github.com/jsr107/RI/issues/45). – Ben Manes Sep 01 '15 at 23:38
  • thanks, Ben. So there is no known solution to this? – mike27 Sep 02 '15 at 14:45
  • I'm sure the concurrent invocations occur because i simply log whenever the method is actually invoked, when the cached value is used nothing gets logged. I use spring cache so i don't manually invoke methods on guava cache – mike27 Sep 02 '15 at 14:47
  • Not that I'm aware of. The arguments by the JSR is that the only value of that feature is rate limiting, which is not what a cache is. Or that the spec doesn't require thread safety, so all locking must be in user code. I find the claims very naive, especially since they come from cache vendors. I don't use Spring, so they may be more reasonable in fixing this. – Ben Manes Sep 02 '15 at 16:25
  • Ben, can you recommend something else than Spring Cache that can be plugged into Spring that doesn't have the issue? – mike27 Sep 03 '15 at 09:21
  • Other than directly using a library, no. You might write your own advise that adds some locking behavior (e.g. using Guava's Striped). I looked at [simple-spring-memcached](https://github.com/ragnor/simple-spring-memcached) at one point but never used it. They may have a something useful to handle this with a caching backend. – Ben Manes Sep 03 '15 at 16:32
  • 1
    @mike27 I searched Spring's issue tracker [SPR-11540](https://jira.spring.io/browse/SPR-11540) rationalizes the lack of support by saying (1) you should preload the cache with everything and (2) it would be too complex to implement. Both are invalid answers, as the easiest approach would be to use a simple array of locks, hash in, and lock around the get/put call (with a get outside to bypass the lock if found). Ideally they would delegate to the cache implementation for a more optimal version. Preloading the entire world defeats the purpose of a cache, imho. – Ben Manes Sep 16 '15 at 19:54
  • I don't think that "too complex to implement" was one of the reasons. Locking has tradeoffs and doing that in the abstraction may not be a good idea. That being said, feel free to contribute a PR Ben. – Stephane Nicoll Sep 27 '15 at 19:54
  • @StéphaneNicoll "we won't implement this at the abstraction level. If you look at BlockingCache the code is quite complicated already and putting that back in a generic abstraction is probably going to be more complicated by an order of magnitude. In the end, a cache has only one goal: being fast. Your application should just run fine with or without cache and, more importantly, adding the cache should not have any side effect to the behaviour of your app. Starting to lock things is not going in that direction." That indicated to me that its considered too complex for the abstraction. – Ben Manes Oct 20 '15 at 18:26

1 Answers1

4

As of Spring Framework 4.3 (still in early development phase at the time of writing) a new flag on @Cacheable called sync is available. If you enable that flag, you opt-in for basically what you're asking.

Spring Framework 4.3 GA is due around May next year but you should see a first milestone in Q1 2016 still. Please give that a try and let us know if that works for you.

Stephane Nicoll
  • 31,977
  • 9
  • 97
  • 89