Search code examples
javaguavaehcache

Does Ehcache use a background thread that removes expired items?


I am using guava cache at this moment and think about moving to Ehcache. According this this post there is no thread that makes sure entries are removed from the cache as soon as the delay has elapsed in guava cache:

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.

The reason for this is as follows: if we wanted to perform Cache maintenance continuously, we would need to create a thread, and its operations would be competing with user operations for shared locks. Additionally, some environments restrict the creation of threads, which would make CacheBuilder unusable in that environment.

But what about Ehcache? Is there a separate thread that removes expired items when memory store is used? I tried to find information, but some say there isn't, some say there is.


Solution

  • TLDR: Ehcache does not offer prompt expiration. Guava cleanups more promptly, but requires external scheduling for a guaranteed coarse granularity. Caffeine can schedule a cleanup based on the next expiration event.

    Ehcache relies upon size eviction to discard expired entries. This means that when space is needed, a live entry may be discarded despite the cache holding many expired dead entries. As Ehcache uses uniform sampling to discover eviction candidates, it must stumble upon a dead entry in order to consider removing it.

    When reviewing the implementation of Ehcache 2.x and 3.x, my understanding is that neither gives prefers to evict expired entries over live entries. The sampled entries are sorted by their lastAccessTime field so that the least recently used entry is evicted. If the expiration times vary, e.g. a shared cache holding both short-lived 2FA tokens and long-lived data, then a significant amount of space can be wasted while waiting for the expired entries to age out. This approach mirrors Memcached and Redis, however due to their very large size those caches use background threads to proactively find and remove dead entries.

    Guava maintains entries on an O(1) priority queue so that it can remove expired entries during maintenance, such as after a write when an eviction might be required. This implements only fixed expiration so that an entry can be stored on a doubly-linked list and reordered to the tail position in constant time. By peeking at the head of the list, Guava can find and prune expired entries immediately. As @LouisWasserman suggested, calling Cache.cleanUp() periodically ensures that an inactive cache will check the list and discard the expired entries. Regardless, the cache will perform maintenance periodically in response to read and write activity.

    Caffeine extends this approach by supporting both per-entry expiration and a scheduler to trigger cache maintenance based on the next expiration event. Variable expiration is implemented in O(1) time using a hierarchical timer wheel. If a scheduling thread is supplied, then the scheduled task will perform maintenance and cause the expired entries to be evicted promptly. In Java 9+, the JVM includes a dedicated scheduling thread which Caffeine can use if configured with Scheduler.systemScheduler().

    LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
        .scheduler(Scheduler.systemScheduler())
        .expireAfterWrite(10, TimeUnit.MINUTES)
        .build(key -> createExpensiveGraph(key));