I am trying to use Google's Guava Cache and I am just testing it with expireAfterWrite with 1 minute intervals. In the application I am working on there are hundreds of users. So my understanding is that when the first person accesses the cache, we fill it from the database and then the one minute timer should start counting down. Now that the cache is filled. We will always get it from the cache for that one minute period. After one minute, it will expire and we get it from the database again.
However, it appears that everytime I access the cache within that one minute period, the timer resets. Why is that? If I don't access the cache within the one minute period the cache expires and works fine.
Code:
Cache<Long, List<someObj>> cacheMap = CacheBuilder.newBuilder().maximumSize(maxSize).expireAfterWrite(maxTime, TimeUnit.MINUTES).removalListener(new RemovalListener<Long, List<someObj>>() {
public void onRemoval(RemovalNotification<Long, List<someObj>> notification) {
if (notification.getCause() == RemovalCause.EXPIRED) {
logger.info("Value " + notification.getValue() + " has been expired");
} else {
logger.info("Just removed for some reason");
}
}
}).build();
...
//Convert to ConcurrentHashMap
cacheMap = someCache.asMap();
...
public List<someObj> getAllData(final Long orgId, final User user) {
// Get requests from cache
List<Request> cacheList = overdueSlaMap.get(orgId);
// Fill cached requests if null
cacheList = fillCacheIfNecessary(orgId, cacheList, overdueSlaMap);
return cacheList;
}
//Strategy for reading and writing to cache
//This method is fine. On first load, cache is empty so we fill it from database. Wait one minute, value expires and cache is empty again so we get from cache.
//However, everytime I refresh the page the one minute timer starts again. Surely, the one minute timer within the Guava Cache should not refresh regardless of how many times I access the cache within that one minute period.
private List<someObj> fillCacheIfNecessary(List<someObj> cacheList, ConcurrentMap<Long, List<someObj>> cacheMap) {
// If the cache is empty then we will try to fill it from the database.
if (cacheList == null) {
logger.info("populating cache");
List<someObj> objList = new ArrayList<someObj>();
// if bla bla
if (such and such) {
objList = service.getFromDatabase(someArg);
} else {
objList = service.getFromDatabase(anotherarg);
}
// Concurrently lock the new cacheList retrieved from the database.
List<someObj> newValue = Collections.unmodifiableList(objList);
// Only insert if new value does not exist in cache map
List<someObj> oldValue = cacheMap.putIfAbsent(orgId, newValue);
// If old value already exists then we use the old value otherwise we use the new value
cacheList = ((oldValue != null && !oldValue.isEmpty()) ? oldValue : newValue);
}
return cacheList;
}
EDIT
I am calling the cache from a controller:
public ModelAndView getNotifications(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
User user = getCurrentUser();
CacheManager cacheManager = new CacheManager();
List<someObj> objList = new ArrayList<Request>();
// Get from database
if (bla bla) {
objList = getFromDatabase(user);
}
// Get from cache
else {
Long orgId = user.getUserOrganisation().getId();
objList = cacheManager.getAllData(orgId, user);
}
return new ModelAndView(SOMEVIEW);
}
which passes in useful data that is needed to the methods which passes it on the the method to call the database. However, I am now tryin to refactor my code to use LoadingCache but this requires me to override the load() method which only uses one parameter which is the key. I need more than just the key and I need to call the cacheManager in the controller, so it calls the appropriate methods. What is the load method? When is it used? When is it called and how often? Does it invalidate my other methods?
I believe your issue is that you are using the
Map
returned fromasMap
as your cache.asMap
is documented asReturns a view of the entries stored in this cache as a thread-safe map
. There is nothing here that suggests you can use theMap
as your cache. I suggest updating your code to use theCache
API and callput
orget(key, Callable)
on the cache directly.You might want to consider using a
LoadingCache
as I have found that the API is simpler.EDIT
So first, the load method is only called when the key does not exist in the cache. The cache calls the load method to get the value that should be returned for they key and then caches that value for use next time. One of the nice things about using a
CacheLoader
or aCallable
is that the call to retrieve the data is thread-safe in that it is only called once per key even if multiple calls for the same key occur at the same time.If you need to pass more information, create a wrapper object as such:
You can also do the same thing using a regular
Cache
and create aCallable
wrapping the values needed.I would suggest that the
CacheLoader
invalidatesfillCacheIfNecessary
. The simple call tocache.get()
will determine internally if the value must be retrieved or already exists in the cache. It is doing theget, check if null, retrieve
work for you.