Description
I was examining the performance in our Spring Boot app. Based on profiling data, I noticed that we have a lot of wait times on RedisCache#get(key, valueLoader)
method.
The method signature makes it obvious why this is happening:
public synchronized <T> T get(Object key, Callable<T> valueLoader) {
In case of our app, it causes all cache lookups to be executed sequentially. (Admittedly a great protection for Redis server 😅).
In the past this method wasn't synchronized. It changed in this commit.
Documentation of the Cache interface states:
Return the value to which this cache maps the specified key, obtaining that value from valueLoader if necessary. This method provides a simple substitute for the conventional "if cached, return; otherwise create, cache and return" pattern.
If possible, implementations should ensure that the loading operation is synchronized so that the specified valueLoader is only called once in case of concurrent access on the same key.
Technically speaking, the current synchronized implementation adheres to documentation. However, so would version that is not synchronized.
For comparison, here's how the EhCacheCache
is implemented:
public <T> T get(Object key, Callable<T> valueLoader) {
Element element = lookup(key);
if (element != null) {
return (T) element.getObjectValue();
}
else {
this.cache.acquireWriteLockOnKey(key);
try {
element = lookup(key); // one more attempt with the write lock
if (element != null) {
return (T) element.getObjectValue();
}
else {
return loadValue(key, valueLoader);
}
}
finally {
this.cache.releaseWriteLockOnKey(key);
}
}
}
I'm happy to submit a PR that changes the implementation. However, before doing so, I'd love to ask if the synchronized method was implemented in the current way deliberately and there's and edge case which makes changing it harder?
LV link