Plugging in different cache managers to spring cache storage

Introduction 

It might be painful to maintain an application specific cache when we are working on a distributed environment mainly  because of the synchronization lag and data inconsistency issue. Best solution for this is to separate the cache to a different server and may be distribute it depending on the storage requirements. 

Ehcache integration

Ehcache is an enterprise grade, feature rich framework for applications requiring a coherent distributed cache.

Caches can be configured in Ehcache either declaratively, XML or programatically .

Integration with ehcache.

 Required dependencies :

[group: 'net.sf.ehcache', name: 'ehcache', version: '2.7.2']

 

Spring cacheManager configuration.

Out of the box, spring provides integration with ehcache.

	 <cache:annotation-driven />
	 <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" 
 		 p:cacheManager-ref="ehcache"/>
 
	 <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" 
     p:configLocation="classpath:/META-INF/spring/ehcache.xml" p:shared="true"/> 

 

ehcache.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
	<defaultCache eternal="true" maxEntriesLocalHeap="100"
		overflowToDisk="false" />
	<cache name="users" maxEntriesLocalHeap="10000" eternal="true"
		overflowToDisk="false" />
	<cache name="tenants" maxEntriesLocalHeap="10000" eternal="true"
		overflowToDisk="false" />
	<cache name="offices" maxEntriesLocalHeap="10000" eternal="true"
		overflowToDisk="false" />
	<cache name="office_template" maxEntriesLocalHeap="10000"
		eternal="true" overflowToDisk="false" />
	<cache name="charges" maxEntriesLocalHeap="10000" eternal="true"
		overflowToDisk="false" />
	<cache name="funds" maxEntriesLocalHeap="10000" eternal="true"
		overflowToDisk="false" />
	<cache name="code_values" maxEntriesLocalHeap="10000" eternal="true"
		overflowToDisk="false" />
	<cache name="codes" maxEntriesLocalHeap="10000" eternal="true"
		overflowToDisk="false" />
	<cache name="roles " maxEntriesLocalHeap="10000" eternal="true"
		overflowToDisk="false" />
</ehcache>

Here I've configured seven inmemory caches. More configuration details can be found in the ehcache configuration documentation.

Configuration parameter details

  • timeToLive

    The maximum number of seconds an element can exist in the cache regardless of use. The element expires at this limit and will no longer be returned from the cache. The default value is 0, which means no TTL eviction takes place (infinite lifetime).

  • timeToIdle

    The maximum number of seconds an element can exist in the cache without being accessed. The element expires at this limit and will no longer be returned from the cache. The default value is 0, which means no TTI eviction takes place (infinite lifetime).

  • eternal 
     when set to "true", overrides timeToLive and timeToIdle so that no expiration can take place.

  • maxEntriesLocalHeap
     Sets the maximum number of objects that will be held on heap memory. 0 = no limit.

What happens when you store more data in ehache than there is memory available?

  • <answer here> 

Note: 

If we need to switch to a disk based cache storage then it is mandatory to implement java.io.serializable interface by  the candidate cache objects. 

Integration with ehcache enterprise

Ehcache enterprise is a solution to distributed cache storage which is not provided in the general ehcache distribution. Now enterprise version is bundled with BigMemomry MAX - an in memory data management platform.

We can download a free copy of enterprise ehcache from here : http://terracotta.org/downloads/bigmemorymax/free

 Required dependencies :

 repositories{
	 maven
	{
		url "http://www.terracotta.org/download/reflector/releases"
	}
}
dependencies{
	compile(
 		[group: 'net.sf.ehcache', name: 'ehcache-ee', version: '2.7.3'],
 		[group: 'org.terracotta', name: 'terracotta-toolkit-runtime-ee', version: '4.0.3']
	)
}

 

It is important to pay attention to dependency versions. According to ehcache documentation all the enterprise dependency versions should matched with the enterprise bundle versions. In case the above configurations failed to resolve dependencies we can host the libraries on a private server.

Spring cacheManager configuration.

CacheManager configurations are same as the previous integration.

	 <cache:annotation-driven />
	 <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" 
 		 p:cacheManager-ref="ehcache"/>
 
	 <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" 
     p:configLocation="classpath:/META-INF/spring/ehcache.xml" p:shared="true"/> 

 

ehcache.xml

In the enterprice edition we can use few other features like server clusters.

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
	<diskStore path="C://data/" />
	<terracottaConfig url="localhost:9510" />
	<cache name="users" maxEntriesLocalHeap="1000" eternal="false"
		timeToIdleSeconds="3600" timeToLiveSeconds="1800">
		<terracotta clustered="true"/>
	</cache>
	<cache name="tenants" maxElementsInMemory="10000" eternal="true"
		overflowToDisk="false" />
	<cache name="offices" maxElementsInMemory="10000" eternal="true"
		overflowToDisk="false" />
	<cache name="office_template" maxElementsInMemory="10000"
		eternal="true" overflowToDisk="false" />
	<cache name="charges" maxElementsInMemory="10000" eternal="true"
		overflowToDisk="false" />
	<cache name="funds" maxElementsInMemory="10000" eternal="true"
		overflowToDisk="false" />
	<cache name="code_values" maxElementsInMemory="10000" eternal="true"
		overflowToDisk="false" />
	<cache name="codes" maxElementsInMemory="10000" eternal="true"
		overflowToDisk="false" />
</ehcache>

 More configuration details can be found on the distributed ehcache configuration documentation.

 

Screen shots of Terracotta Management console

 

 

Memcached integration 

Memcached is a widely used, FOS, high performance, in memory object caching framework. I had to change the previously chosen key values a little bit to suit memcached framework requirements.

We can consider memcached as a giant hash map which stores key value pairs.

There are several memcached clients available for java ; xmemcached , spymemcached , Memcached-java-client . A comparison of these three clients can be found in here. After going through the comparison and little research on the internet I've choose xmemcached client . 

 Required dependencies :

	[group: 'com.google.code.simple-spring-memcached', name: 'xmemcached-provider', version: '3.2.0'],
    [group: 'com.google.code.simple-spring-memcached', name: 'spring-cache', version: '3.2.0']

Memcached by default does not provide support for cache zones as spring does. However 'Simple Spring Memcached' provides the solution for it.

memcached.xml for user cache :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
	<!-- Cache manager -->
	<bean name="cacheManager" class="com.google.code.ssm.spring.ExtendedSSMCacheManager">
		<property name="caches">
			<set>
				<bean class="com.google.code.ssm.spring.SSMCache">
					<constructor-arg name="cache" index="0" ref="users" />
					<constructor-arg name="expiration" index="1" value="0" />
					<constructor-arg name="allowClear" index="2" value="true" />
				</bean>
			</set>
		</property>
	</bean>
	<bean name="users" class="com.google.code.ssm.CacheFactory">
		<property name="cacheName" value="users" />
		<property name="cacheClientFactory">
			<bean name="cacheClientFactory"
				class="com.google.code.ssm.providers.xmemcached.MemcacheClientFactoryImpl" />
		</property>
		<property name="addressProvider">
			<bean class="com.google.code.ssm.config.DefaultAddressProvider">
				<property name="address" value="127.0.0.1:11211" />
			</bean>
		</property>
		<property name="configuration">
			<bean class="com.google.code.ssm.providers.CacheConfiguration">
				<property name="consistentHashing" value="true" />
			</bean>
		</property>
	</bean>
</beans>

 

Since memcached doesn't have cache zone concept, a cache zone specific identifier was appended to the key values to avoid possible conflicts on a single memcached instance:

 

Cache ZoneIdentifer
codescd
fundsfn
usersun
chargesch
officesof
office_tempatesoft
code_valuescdv
rolesrl

 

Charges cache configuration 

 @Cacheable(value = "charges", key = "T(org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('ch')")
    public Collection<ChargeData> retrieveAllCharges() {

 

What happens when you store more data in memcached than there is memory available?

  •  memcached throws away data that is least recently used. (LRU)

Memcached management console

PhpmemcachedAdmin is a handy tool to monitor memcached instances it is still in the beta version.

Cached API collection - POSTMAN

I've created a postman collection for cached API end points. One can easily import them to POSTMAN and check the performance improvement by observing the request time. 

Download link

Additional resources