Application Caching using Ehcache
Ehcache is an open source caching system that handles application caching.
Advantages
Using Ehcache gives Self-Managed Commerce the following benefits:
- JMX Support
- Unified run-time configuration for application and OpenJPA L2 caches
- Improved cache visibility and control
Application caching is enabled in Cortex, Search Server, Integration Server, and the Import/Export CLI tool.
Cache Behaviour
Most application caches are initialized using the EhcacheCacheFactory
class. This creates a new cache in the EhCache cache manager and then wraps the result using EhcacheCacheAdapter
which implements the com.elasticpath.cache.Cache
interface.
The com.elasticpath.cache.Cache
interface exposes several important methods which are described below.
CacheResult<V> get(K key)
This simple method retrieves the value from the cache for the passed key. The CacheResult
object allows the caller to determine if the object was present in the cache by calling the isPresent
method. It is possible to cache a null result, which will return true when isPresent
is invoked, but the get
method will return null.
It is usually recommended to use the get
method with the fallbackLoader
parameter instead of this method to ensure thread safety.
V get(K key, Function<K, V> fallbackLoader)
This method retrieves the value from the cache for the passed key and falls back to the fallbackLoader
function to retrieve the value if it isn't present. The result from the fallbackLoader
is inserted into the cache.
This method also uses XSync to ensure that if multiple threads request the same key value, only the first will invoke the fallbackLoader
and the other threads will block until the fallbackLoader
completes. After the value is loaded, all of the waiting threads will receive this value.
V get(K key, Function<K, V> fallbackLoader, BiFunction<K, V, V> populateCacheFunction)
This method retrieves the value from the cache for the passed key and falls back to the fallbackLoader
function to retrieve the value if it isn't present. The populateCacheFunction
is called after the fallbackLoader
function completes, and allows more complex cache population logic. It is the responsibility of the populateCacheFunction
to populate the cache, if necessary.
This method also uses XSync to ensure that if multiple threads request the same key value, only the first will invoke the fallbackLoader
and the other threads will block until the fallbackLoader
completes. After the value is loaded, all of the waiting threads will receive this value.
Map<K, V> getAll(Collection<? extends K> keyValues)
This method retrieves multiple value from the cache for the passed keys. Each result is returned in the map associated to it's corresponding key.
Map<K, V> getAll(Collection<K> keyValues, Function<Collection<K>, Map<K, V>> fallbackLoader)
This method retrieves multiple value from the cache for the passed keys. For any values not present in the cache, a single call to the fallbackLoader
function is made to retrieve the missing values. The results from the fallbackLoader
are inserted into the cache as separate entries.
V put(K key, V value)
This method inserts a single value into the cache corresponding to the passed key.
boolean remove(K key)
This method removes a single value from the cache for the passed key.
void removeAll()
This method removes all values from the cache.
boolean containsKey(K key)
This method returns true if the specified key exists in the cache.
Configuration
Elastic Path provides the following Ehcache configuration file in the extensions/database/ext-data
module:
src/main/resources/environments/default/files/conf/cache/ehcache-fast-updates.xml
for use in development environments
For development use, you need to copy configuration files to your ${user.home}/ep/conf
directory as described in Updating Configuration Files.
For deployed environments, the configuration file is specified using the ep.external.ehcache.xml.path
property, either as a JVM parameter or in the ep.properties
file.
The file path in ep.external.ehcache.xml.path
must be an absolute path. There are three supported formats as shown in the following examples:
${user.home}/ep/conf/cache/ehcache.xml
(Linux and Windows)/ep/conf/cache/ehcache.xml
(Linux) orc:/ep/conf/cache/ehcache.xml
(Windows)file:///ep/conf/cache/ehcache.xml
(Linux) orfile:///c:/ep/conf/cache/ehcache.xml
(Windows)
Additional Slash
The additional slash in file:///
is required by Spring.
If a particular cache is not specified in the file referenced by ep.external.ehcache.xml.path
(or if the ep.external.ehcache.xml.path
value is not set), then the cache will use the default values specified in the Spring bean. The Spring bean defaults represent the values that Elastic Path recommends for most production-like environments.
All Ehcaches can also be completely disabled by setting the -Dnet.sf.ehcache.disabled=true
JVM parameter.
An application restart is required for configuration changes to take effect.
Cortex and Integration Server Application Caches
The application level caches sit above the data access layer and are used by Elastic Path to cache longer lived application data.
Application cache configuration (time-to-live, time-to-idle, and max entries) can be defined in the external configuration file described above. If no external Ehcache file is defined, or if the file doesn’t contain an entry for a cache, then the fallback configuration is defined by the cache bean definition in Spring. See Application Data Caching a for a list of caches.
Search Server Application Caches
In the Search Server, many application caches have their configuration overridden to a shorter time-to-live. This is done to ensure that the search server doesn’t index out-of-date cached values.
By default, all caches, except domain (starting with com.elasticpath
) and openjpa-query
, have time-to-live and time-to-idle overridden to 5 seconds by the ehCacheConfigurationConfigurator
bean.
These values can be changed via system properties, if required:
-Dep.ehcache.ttl.seconds.override=<N>
-Dep.ehcache.tti.seconds.override=<N>
Additionally, which caches are overridden by the ehCacheConfigurationConfigurator
bean can be configured in its Spring properties:
cachesToOverride
: the list of caches for which time-to-live/time-to-idle will be overriddencachesToExclude
: the list of caches that will be excluded from overridingdomainCacheNamePrefixesToExclude
: the list of domain cache prefixes that will be excluded from overriding
Cache Invalidation
Objects in the Ehcache can be evicted for two reasons:
- The time-to-live (seconds since object was added to the cache) or time-to-idle (seconds since object was accessed in the cache) expires. This is managed internally by Ehcache.
- The object was modified by Commerce Manager, Import/Export, or Data Sync, and message-based cache invalidation is supported for the object type.
Message-based cache invalidation is currently only supported for product objects, but support for this will be expanded to other object types in upcoming versions. When an object is updated, a message is sent to the ep.domain
JMS queue. This is processed by the cacheEventMessageRouteBuilder
Camel route in the Integration Server, which then sends a message to the ep.cache
topic. All running services listen to this topic, and evict the object referenced in the message.
Messages on the ep.cache
topic are formatted as follows:
{
"eventType": {
"@class": "CacheEventType",
"name": "PRODUCT"
},
"guid": "{PRODUCT_CODE}",
"data": {}
}
The diagram below shows the interactions between JMS queues and the Self-Managed Commerce services:
OpenJPA L2 Cache
OpenJPA uses ehCache as a backing store for its level 2 JPA cache.
The default Ehcache OpenJPA L2 cache configuration is defined in ehcache.xml
in the commerce-engine/core/openjpa-osgi-wrapper
module.
<defaultCache
maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="1"
timeToLiveSeconds="1"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
statistics="true"
/>
L2 Data Cache
The L2 (Level 2) data cache holds entity instances using their primary keys as identifiers. A separate cache is created for each domain entity with a cache name of the fully qualified class name (e.g. com.elasticpath.domain.catalog.impl.ProductImpl
).
Entities will not be stored in the data cache if they have the @DataCache(enabled = false)
annotation set. We recommend that the data cache be completely disabled for entities that are modified by shoppers, such as shopping cart entities.
The time-to-live for each data cache can be set in the following ways, evaluated from top to bottom:
- A timeout (in seconds) can be set in the data cache annotation as follows:
@DataCache(timeout = 3600)
- An explicit
cache
entry can be specified for the entity using its fully-qualified class name. - The
openjpa.DataCacheTimeout
JPA property can be set for all entities. - If none of the above overrides are set, the
timeToLiveSeconds
attribute of thedefaultCache
is used.
We recommend against using a data cache with more than a one-second timeout, as this can lead to dirty reads and inconsistent behaviour.
L2 Query Cache
The L2 (Level 2) query cache caches query results so that repeated queries with the same parameters can retrieve results directly from the cache rather than hitting the database again. The cache holds the list of primary keys for the entities that match the query criteria, not the actual entities themselves which are stored in the L2 data cache.
The OpenJPA query cache is configured by the following element:
<cache name="openjpa-querycache"
maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600"
statistics="true"
/>
Configuration Example
Here is a sample configuration override file:
note
For information on Ehcache’s configurable properties, see Ehcache’s Cache Configuration guide.
<?xml version='1.0' encoding='UTF-8'?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"
name="ep-default-cache"
updateCheck="false">
<!--
Read the Ehcache documentation for help on configuring this file.
The <defaultCache> configuration is used as the default for programmatically created caches.
<cache> entries will not inherit configuration from <defaultCache>.
-->
<defaultCache
timeToIdleSeconds="1"
timeToLiveSeconds="1"
maxEntriesLocalHeap="10000"
eternal="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
statistics="true"
/>
<cache name="openjpa-querycache"
timeToIdleSeconds="5"
timeToLiveSeconds="5"
maxEntriesLocalHeap="10000"
eternal="false"
statistics="true"
/>
<!--
Override application cache configuration.
-->
<cache name="baseAmountCache"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600"
maxEntriesLocalHeap="10000"
statistics="true"
/>
</ehcache>
An example of overriding all caches can be found in the extensions/database/ext-data/src/main/resources/environments/default/files/conf/cache/ehcache-fast-updates.xml
file.
Recommended Practices
Every commerce implementation is unique. No one configuration fits all. You will need to discover the configurations that work for your implementation. Keep in mind:
- Configure lower cache timeouts in development, QA and authoring environments where users want to see changes quickly and performance isn’t a primary consideration.
- Configure much higher timeouts for production and performance test live environments.
- Stress your application while using JMX console to monitor the number of cache requests. This will help you determine if the DB entities needs to be cached and for how long.
- Be cautious about increasing L2 cache timeouts. Complex domain object graphs, such a products or categories, can get out of sync when L2 objects are refreshed independently of each other.
- Keep conducting performance tests until the optimal results are achieved.
- Ehcache documentation contains comprehensive information about sizing caches.
Switchable Proxy Binder
Within the ep-core
module, all service class beans that support application-level caching are defined using the com.elasticpath.commons.beanframework.SwitchableProxyFactoryBean
class. SwitchableProxyBinder
allows a bean definition to use the default non-caching implementation of a service unless a caching implementation is available.
For non-OSGi webapps (such as Integration Server), this allows us to optionally include the ep-core-caching
dependency, and the bean definition will switch to the caching or non-caching service automatically, depending on whether the ep-core-caching
dependency is present.
For OSGi webapps (such as Cortex), this allows us to reference a caching service from OSGi if it's available, otherwise use the default non-caching service defined in Spring. As a result, if the ep-core-caching
bundle is unavailable or inactive, the non-caching service will be used.
How does it work?
Services with a caching override class declare their bean using SwitchableProxyFactoryBean
as in the following example:
<bean id="ruleServiceProxy" class="com.elasticpath.commons.beanframework.SwitchableProxyBinder">
<property name="fallbackImplementation" ref="nonCachingRuleService"/>
</bean>
<bean id="ruleService" class="com.elasticpath.commons.beanframework.SwitchableProxyFactoryBean">
<property name="proxyInterface" value="com.elasticpath.service.rules.RuleService"/>
<property name="proxy" ref="ruleServiceProxy"/>
</bean>
The SwitchableProxyFactoryBean
class implements the Spring Framework FactoryBean
interface, so whenever Spring needs to inject this bean into another bean that requires it, the SwitchableProxyFactoryBean#getObject
method is called. This method returns a proxy which delegates all method calls to the object returned by SwitchableProxyBinder#getProxy
.
The SwitchableProxyBinder
class returns the fallbackImplementation
object unless a boundImplementation
object has been set. The boundImplementation
field is set by calling SwitchableProxyBinder#bindImplementation
. This is called in one of two ways:
For non-OSGi webapps (such as Integration Server), if the ep-core-caching
dependency is present, the cache-spring-overrides.xml
calls bindImplementation
for each SwitchableProxyBinder
bean, as in the following example:
<bean factory-bean="ruleServiceProxy" factory-method="bindImplementation">
<constructor-arg ref="cachingRuleService"/>
</bean>
This causes the cachingRuleService
to be used instead of the fallback nonCachingRuleService
.
For OSGi webapps (such as Cortex), the core-blueprint.xml
looks up an OSGi service for the RuleService
interface, and when it's found, calls bindImplementation
for the corresponding SwitchableProxyBinder
bean, as in the following example:
<reference id="cachingRuleService"
availability="optional"
interface="com.elasticpath.service.rules.RuleService"
filter="(caching=true)"
timeout="1">
<reference-listener ref="ruleServiceProxy" bind-method="bindImplementation" unbind-method="unbindImplementation"/>
</reference>
This reference will find any OSGi service that implements the RuleService
interface and has the caching=true
property defined.
<service ref="cachingRuleService" interface="com.elasticpath.service.rules.RuleService" ranking="200">
<service-properties>
<entry key="caching">
<value type="java.lang.Boolean">true</value>
</entry>
</service-properties>
</service>
Extending services that use the Switchable Proxy Binder
If you need to extend a service with a bean that uses the SwitchableProxyBinder
, you will also need to extend the corresponding caching service class and export it through OSGi.
Continuing the RuleService
extension example, assume that you have already done the following:
- Created an
ExtRuleService
interface inext-core
which extendsRuleService
and declared your new methods. - Created an
ExtRuleServiceImpl
class inext-core
which implementsExtRuleService
and extendsRuleServiceImpl
. - Overridden the
ruleServiceLocal
bean definition inextensions/core/ext-core/src/main/resources/META-INF/conf/ep-core-plugin.xml
to point to theExtRuleServiceImpl
class.
Now you need to extend the caching service class and make it visible to the SwitchableProxyBinder
:
Create a
CachingExtRuleServiceImpl
in theext-core-caching
module, which extendsCachingRuleServiceImpl
and implements theExtRuleService
interface.Declare a new bean for
cachingRuleService
in one of theext-core-caching
Spring configuration files, such asextensions/core/ext-core-caching/src/main/resources/spring/core-caching/ext-cache-example.xml
. You may want to create a new XML file and import it inextensions/core/ext-core-caching/src/main/resources/spring/core-caching/plugin.xml
.Declare an OSGi reference to the
nonCachingRuleService
inextensions/core/ext-core-caching/src/main/resources/OSGI-INF/blueprint/ext-core-caching-blueprint.xml
:<reference id="nonCachingRuleService" interface="com.elasticpath.extensions.service.rules.ExtRuleService" filter="(caching=false)"/>
Export the
cachingRuleService
bean as an OSGi service inextensions/core/ext-core-caching/src/main/resources/OSGI-INF/blueprint/ext-core-caching-blueprint.xml
:<service ref="cachingRuleService" interface="com.elasticpath.extensions.service.rules.ExtRuleService" ranking="200"> <service-properties> <entry key="caching"> <value type="java.lang.Boolean">true</value> </entry> </service-properties> </service>
Override the
nonCachingRuleService
OSGi reference inextensions/core/ext-core/src/main/resources/OSGI-INF/blueprint/ext-core-blueprint.xml
:<service ref="nonCachingRuleService" interface="com.elasticpath.extensions.service.rules.ExtRuleService" ranking="100"> <service-properties> <entry key="caching"> <value type="java.lang.Boolean">false</value> </entry> </service-properties> </service>
After these changes, the ruleService
SwitchableProxyBinder
bean should use the caching implementation of the service automatically.
Determining if extensions are registered properly
Start up your webapp and check the console output. If any of the SwitchableProxyBinder
beans are using the fallback implementation, you will see a warning similar to the following:
WARN: Using fallback implementation of 'com.elasticpath.service.pricing.PriceListAssignmentService'. This usually means that caching is disabled for this service. See https://documentation.elasticpath.com/commerce/docs/core/platform/cross-platform/ehcache.html#switchable-proxy-binder for more information.
However, if you see a follow-up information message similar to the following, then the warning can be ignored:
INFO: A binding implementation of 'com.elasticpath.service.pricing.PriceListAssignmentService' has been set. This usually means that caching was late-bound for this service, and the earlier warning can be ignored.
note
These log messages were enabled by a recent patch (Jul 2025). Make sure you are up-to-date on the latest patches for your version of Self-Managed Commerce.