Home » Software Development Resources » Tier-ing It Up with Ehcache

Tier-ing It Up with Ehcache


code {
background-color: #eee;
font-style: oblique;
}

Ehcache is a Java, open-source cache solution maintained by Software AG. It is commonly used to improve a Java application’s performance. This is done by storing frequently accessed and fairly static data locally in the application, either in memory or on disk.

Recently, I was conducting a technology refresh and upgraded Ehcache from version 2.x to 3.x. Since Ehcache was designed based on community base practices and standards, the architecture has not changed. However, changes to the configuration and instantiation were made to follow the fluent interface design pattern.  This caused some key changes to the configuration and forced me to dig into Ehcache a little more in-depth. One such area was Tiering.

This article is not intended to be an in-depth discussion on Tiering, but to give the reader a simple look at its use. For an in-depth discussion please visit https://www.ehcache.org/documentation/3.10/caching-concepts.html#storage-tiers

Geo caching

Tiering

The concept of tiering in Ehcache is to provide the quickest possible access to the cached objects while allowing for the most possible data to be stored in the cache. If a large enough application can only store so much data in the Java heap memory, what happens when this data grows larger than the allowable heap? It can either be evicted or moved off the heap. Moving data off the heap is the general principle of how Ehcache’s tiering works. The three tiers are Heap, Off-Heap, and Disk.

Heap

Heap storage utilizes the Java heap RAM and is the fastest cache storage available, but also the most limited in terms of byte size. The more data you cache here, the more it will impact the overall application. In addition, this storage is scanned by the garbage collector. Ehcache allows heap storage to be configured in byte size or a number of cached objects in the heap.

Off-Heap

This storage is fast but not as fast as the heap. The only size limitation to off-heap storage is the available RAM. This storage is not scanned by the garbage collector. This tier is configured with the allowable byte size. Since objects are moved from the heap to off-heap they will need to be serialized and need to implement the Serializable interface.

Disk

Like Off-Heap, the disk tier serializes data and moves it off the heap, but onto the local disk. This is the slowest cache tier but offers the most in terms of byte size potential. The disk is configured using byte size and has the limitation that it can be used with distributed caches. A persistent strategy is used to configure this tier.

Demo

In this simple demo, I will show you how to configure, retrieve, and how data is stored using the heap and disk storage tiers.

 

StatisticsService statisticsService = new DefaultStatisticsService();
// The cache manager is closable resource. Clean it up after we are done
try (PersistentCacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
        .with(CacheManagerBuilder.persistence("/tmp/ehcache-data"))
        .using(statisticsService)
        .withCache(CACHE_NAME,
                CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class,
                        Music.class, ResourcePoolsBuilder
                                .heap(3).disk(1, MemoryUnit.GB)))
        .build(true)) {

    Music music1 = new Music("ISRC11234566", "Plush", "Stone Temple Pilots", "Atlantic");
    Music music2 = new Music("ISRC1234567", "Tumble in the Rough", "Stone Temple Pilots", "Atlantic");
    Music music3 = new Music("ISRC11234568", "Black", "Pearl Jam", "Epic");
    Music music4 = new Music("ISRC11234569", "Jeremy", "Pearl Jam", "Epic");
    Music music5 = new Music("ISRC11234579", "Deep", "Pearl Jam", "Epic");

    Cache<String, Music> musicCache = cacheManager.getCache(CACHE_NAME, String.class, Music.class);
    musicCache.put(music1.getId(), music1);
    musicCache.put(music2.getId(), music2);
    musicCache.put(music3.getId(), music3);
// Place a debug breakpoint on the line below
    musicCache.put(music4.getId(), music4);
    musicCache.put(music5.getId(), music5);
    
// Place a debug breakpoint on the line below
    Music music1Cache = musicCache.get(music1.getId());
    System.out.println("Music 1: " + music1Cache.toString());
    System.out.println("Logic equal 1: " + music1Cache.toString().equals(music1.toString()));

    Music music5Cache = musicCache.get(music5.getId());
    System.out.println("Music 5: " + music5Cache.toString());
    System.out.println("Logic equal 5: " + music5Cache.toString().equals(music5.toString()));

    musicCache.forEach(entry -> System.out.println(entry.getValue().toString()));
    printStats( statisticsService.getCacheStatistics(CACHE_NAME));

}

Setting up the Cache

In the first chunk, we see the cache manager, and the cache configured. Creating and configuring Ehcache changed in 3.x when the developers decided to follow a fluent interface pattern. A cache manager can contain multiple different caches, each identified with a string id.

In this example, we are configuring statistics tracking, which is optional and not well documented in the 3.x documentation. So I’m including it for your reference.

First, we configure the cache manager and place it in a try-with-resource block since it implements the Closable interface.

 

CacheManagerBuilder.newCacheManagerBuilder()
  .with(CacheManagerBuilder.persistence("/tmp/ehcache-data"))
  .using(statisticsService)
  .withCache(<id>, <Cache-configuration>
  .build(true)

The above block creates and builds a new cache manager. Since we are configuring a disk tier we need to use .with() to specify where the disk cache will be stored. This also tells the builder to return a PersistentCacheManager class. We add .using() to pass in the StatisticsService. The using method can be left off if statistics aren’t needed. The .withCache() method adds the cache to the cache manager and multiple .withCache() methods can be chained together if multiple caches are needed. Lastly, we use .build(true) it to create the cache manager. We pass in true to tell the builder to initialize the cache manager. Otherwise, the application will need to explicitly call .init() on the cache manager after the builder is finished.

In this example, we are only creating one cache.

CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, Music.class, 
ResourcePoolsBuilder.heap(3).disk(1, MemoryUnit.GB))

For the cache, we will use a CacheConfigurationBuilder. The first two method fields are the key and value that the cache will store. The string is the id and Music is the serializable class.  If the cache is using off-heap or disk tiering the key and values need to be serializable. The last field uses the ResourcePoolsBuilder to create and configure the tiers. Here we use .heap() to configure the heap with the number of cache objects to keep in the heap. In the above case, we are telling the cache to keep 3 objects in the heap tier. The heap can also be configured using bytes.

After the heap, we configure the .disk() tier. If more than three items are added to the cache then the cache manager will use the cache advice to serialize and move an object out of the heap and onto the disk. In this configuration, we tell the cache to use 1 GB of disk space for caching. If we exceed 3 items in the heap and 1 GB of disk space the cache will evict an item from the cache to make space.

In the middle section of the application, we are creating items and add them to the cache.

At the bottom, we are retrieving the items and testing them, looping through all the items in the cache, and printing them out. Lastly, we check the cache statistics using the StatisticsService.

Running

Before we run this application let’s place two debug breakpoints and check the file system for cached data. We will place the first breakpoint at

musicCache.put(music4.getId(), music4);

and

Music music1Cache = musicCache.get(music1.getId());

Now let’s run the application and check the specified cache directory at the first breakpoint

Here we can see the first cache entry has been serialized and moved out of the heap and onto disk. Now if we move to the second break point.

We now see additional items have been moved from the heap.

Summary

In this article, we see how to configure Ehcache with the heap and disk tier. We also peak into the disk cache to see the items stored on the disk tier at runtime. The hope is that this article will give another example of how to configure and use Ehcache. For more detailed configuration I encourage you to check out the resources below. You can also access this article’s source code on Github.

 

Resources

 

Scroll to Top