I'm trying to use caffeine cache and have a problem:
Let's say cache is empty and I query for a value, it uses loader and loads a new value into the cache, after 2 days have passed I query for the same value and I get OLD value first then refresh is initiated on a separate thread and the new value is loaded if loading is possible.
Caffeine.newBuilder()
.refreshAfterWrite(5, TimeUnit.MINUTES)
.expireAfterWrite(3, TimeUnit.DAYS)
.build(loader);
What I want to archive is - try to refresh first and return the new value first if possible, if something goes wrong only then return the old one. How can I archive this? How to implement simple, future proof, neat implementation, without workarounds? That would be awesome!
Edit: Would this solution work properly?
boolean needsSyncRefresh = cache.policy().expireAfterWrite()
.flatMap(stringBigDecimalExpiration -> stringBigDecimalExpiration.ageOf(key))
.filter(age -> age.toMinutes() < 0 || age.toMinutes() > REFRESH_AFTER_MINUTES)
.isPresent();
V value = cache.get(key);
if (needsSyncRefresh) {
return cache.asMap().computeIfPresent(key, (k, oldValue) -> {
if (oldValue.equals(value)) {
try {
return loader(key);
} catch (Exception e) {
//handle error
}
}
return oldValue;
});
}
return value;
I don't think using CacheWriter is necessary here. Instead inspect the metadata to determine if a refresh is needed.
The simplest might be to run a scheduled task that triggers a refresh early. For example,
var policy = cache.policy().expireAfterWrite().get();
for (var key : cache.asMap().keySet()) {
boolean refresh = policy.ageOf(key)
.filter(age -> age.toDays() >= 2)
.isPresent();
if (refresh) {
cache.refresh(key);
}
}
A slightly more complicated version does this at retrieval by checking if the retrieved value is stale and performing a new computation, e.g.
V value = cache.get(key, this::loadValue);
var policy = cache.policy().expireAfterWrite().get();
boolean refresh = policy.ageOf(key)
.filter(age -> age.toDays() >= 2)
.isAbsent();
if (!refresh) {
return value;
}
return cache.asMap().compute((key, oldValue) -> {
if ((oldValue != null) && (oldValue != value)) {
return oldValue; // reloaded by another thread
}
try {
return loadValue(key);
} catch (Exception e) {
logger.error("Failed to load {}", key, e);
return oldValue;
}
});