Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LoadingCache expireAfterWrite not working as expected

I am having a Loading Cache like this :

MyCacheLoader loader=new MyCacheLoader();
MyRemovalListener listener=new MyRemovalListener();
LoadingCache<String, String> myCache = CacheBuilder.newBuilder()
                .concurrencyLevel(10)
                .weakKeys()
                .maximumSize(2)
                .expireAfterWrite(120, TimeUnit.SECONDS)
                .removalListener(listener)
                .build(loader);

But when I do something like this to test :

System.out.println(myCache.get("100"));
System.out.println(myCache.get("103"));
System.out.println(myCache.get("110"));
System.out.println(myCache).get("111"));
Thread.sleep(230000);
System.out.println(myCache.size());

Am still getting 2 and not zero. Why ? After more than 120 seconds I should have got zero size if am not wrong ?

MyCacheLoader

public class MyCacheLoader extends CacheLoader<String,String>     {
    @Override
    public String load(String key) throws Exception {
        return key;
    }
}

MyRemovalListener

public class MyRemovalListener implements RemovalListener<String,String> {

    Logger logger = LoggerFactory.getLogger(MyRemovalListener.class);
    @Override
    public void onRemoval(RemovalNotification<String, String> removalNotification) {
        logger.info("Message with message Id ("+
                removalNotification.getKey()+ ") is removed.");
        System.out.println("Message with message Id ("+
                removalNotification.getKey()+ ") is removed.");
    }
}
like image 857
HackAround Avatar asked Mar 12 '23 04:03

HackAround


1 Answers

First, if you are new to Guava's Cache API or if you are treating it as if it implements the Map<K, V> interface then I recommend reading CachesExplained · google/guava Wiki. "A Cache is similar to ConcurrentMap, but not quite the same."

e.g. Cache.size() "returns the approximate number of entries in this cache" (emphasis added) unlike Map.size() which "returns the number of key-value mappings in this map."

As such, we should not expect Cache.size() to return an exact number of entries but an approximate one. I would not generally make assertions on Cache.size() although I certainly would on Map.size().

Second, "caches built with CacheBuilder do not perform cleanup and evict values 'automatically,' or instantly after a value expires, or anything of the sort. Instead, it performs small amounts of maintenance during write operations, or during occasional read operations if writes are rare" (When Does Cleanup Happen? · CachesExplained · google/guava Wiki).

Consider the following example:

LoadingCache<String, String> myCache = CacheBuilder.newBuilder()
        .concurrencyLevel(10)
        .weakKeys()
        .maximumSize(2)
        .expireAfterWrite(120, TimeUnit.MILLISECONDS)
        .removalListener(notification ->
                System.out.println(notification.getKey() + " removed"))
        .build(new CacheLoader<String, String>() {
            @Override
            public String load(String key) throws Exception {
                System.out.println("loading " + key);
                return UUID.randomUUID().toString();
            }
        });
myCache.get("100");
myCache.get("103");
myCache.get("110");
myCache.get("111");
Thread.sleep(230);
System.out.println(myCache.size());
myCache.get("111");
System.out.println(myCache.size());

Ouptut:

loading 100
loading 103
loading 110
100 removed
loading 111
103 removed
2
110 removed
111 removed
loading 111
1

As you can see, evictions here do not occur until items are read from the cache.

For more details along with a more detailed example see my answer to How does timed cache expiry work?

like image 112
mfulton26 Avatar answered Mar 27 '23 08:03

mfulton26