Announcement: You can find the guides for Commerce 7.5 and later on the new Elastic Path Documentation site. This Developer Center contains the guides for Commerce 6.13.0 through 7.4.1.Visit new site

This version of Elastic Path Commerce is no longer supported or maintained. To upgrade to the latest version, contact your Elastic Path representative.

Request-Scoped Caching

Request-Scoped Caching

Cortex can cache objects for the duration of an HTTP request to improve performance. Cached objects are automatically evicted from cache when the request is completed. Request-scoped caching is controlled by adding @CacheResultand @CacheRemove annotations to methods in Cortex repository classes.

To enable request-scoped caching on a repository's methods, you must first ensure that AspectJ can detect your repository to weave the repository with caching annotations, and then you must apply the annotations.

Enabling Caching on a Repository

As of Cortex 6.18 repositories use the @Component annotation. To enable request-scoped caching on repositories, ensure the following prerequisites are met:

  1. Ensure that your repository is located in package following the naming pattern: *.integration.epcommerce.repository, where * is any prefix. For example, com.elasticpath.rest.resource.integration.epcommerce.repository would be an appropriate package.
  2. Ensure that your repository's class name ends with RepositoryImpl. For example, CarsRepositoryImpl.

@Named Legacy Repositories

Legacy repositories using the @Named annotation require the above steps, as well as the following steps:

If the package name does not start with com.elasticpath.rest, then the package name must be added to the <context:component-scan/> element in resources\OSGI-INF\blueprint\ext-applicationContext-repositories-integration.xml:

<context:component-scan base-package="my.pkg.with.custom.repos" scope-resolver="org.springframework.context.annotation.Jsr330ScopeMetadataResolver"/commerce-legacy/>

When adding multiple packages to <context:component-scan/>, the values should be comma separated:

<context:component-scan base-package="my.pkg.with.custom.repos,my.pkg.with.custom.repos2" scope-resolver="org.springframework.context.annotation.Jsr330ScopeMetadataResolver"/commerce-legacy/>

Caching Method Responses

Once you have ensured that your repository is in the correct package and named correctly, you can cache method responses by annotating them with the @CacheResult annotation.
@CacheResult
  public Customer getCustomer(CustomerId customerId){}

In most cases, the @CacheResult annotation is the only thing needed to enable caching.

For Helix programming model repositories using the @Component annotation, the AspectJ weaving process occurs at run time when the resource's URI is accessed. For legacy repositories using the @Named annotation, AspectJ weaving occurs at load time when Cortex starts up.

Invalidating Caches on Methods

To invalidate cached values when running update or delete methods, annotate the methods with the @CacheRemove annotation. @CacheRemove requires either an object of the class to be invalidated or an array of class objects. The value to invalidate equals the return type of the corresponding get method.

@CacheRemove(typesToInvalidate = Customer.class)
public void deleteCustomer(CustomerId customerId) {}

@CacheRemove(typesToInvalidate = Customer.class)
public void deleteAllCustomers() {}  
For nested generics, this class should be the unchecked simplest form. For ExecutionResult return type, the wrapper class should be ignored and the nested type used. The following translation table applies:
Return-type typesToInvalidate Description
Store Store.class No generic type used thus class type can be directly derived from the return type.
ExecutionResult<Store> Store.class ExecutionResult wrapper is omitted, the type-hint rule for the declared generic type applies.
Map<String, List<String>> Map.class Nested generic type definitions are flatten and class type.
ExecutionResult<List<String>> List.class When defining ExecutionResult in combination with a nested generic type omitting ExecutionResult and generic type flattening applies.

Tuning Request-Scoped Caching Performance

To achieve the best request-scoped caching performance, use the tuning techniques described below.

Defining Key Variants

Important: Key Variants Must be Unique

Key variants have to be unique otherwise inconsistencies might occur.

Domain objects may have multiple key variants. A call to a @CacheResult annotated method caches the returned domain object under all its key variants. A subsequent call to another @CacheResult annotated method, which has input parameters matching a key variant, will result in a cache hit and the cached value being returned. For example:
class Customer {
	String storeCode,userId; //key variant - unique identifier
	String guid //key variant - unique identifier
}

@CacheResult//first call goes here
public Customer findCustomerByUserId(String storeCode, String userId) {}

@CacheResult//second call goes here, leverages key variant caching
public Customer findCustomerByGuid(String guid) {}
To enable the cache to use key variant optimization, register the domain object and its key variants by implementing the CacheKeyVariants interface and registering this interface with the CacheKeyManager. For example:
@Singleton
@Named
public final class CustomerCacheKeyVariants implements CacheKeyVariants<Customer> {

	@Inject
	public CustomerCacheKeyVariants(
			@Named("cacheKeyManager")
			final CacheKeyManager cacheKeyManager) {
    //register the CacheKeyVariant to use it
		cacheKeyManager.register(this);
	}

	@Override
	public Collection<Object[]> get(final Customer customer) {
		return Arrays.asList(
        // the keys have be unique identifier, otherwise inconsistencies might occur
				new Object[]{customer.guid},
				new Object[]{customer.storeCode, customer.userId}
		);
	}

	@Override
	public Class<Customer> getType() {
		return Customer.class;
	}
}

How Cache Invalidation Works

Request-scoped caches are divided into cache regions, each region represents the class that instantiated the cache. Cached values are only available to the class that originated the cache. The same applies for invalidation, only the originating class can invalidate the cache.

Getting the Best Cache Invalidation Performance

We achieve the best performance results by invalidating the least amount of cache entries as possible. Below is a list of some important cache invalidation approaches.

First Way: Parameter Equality Matching

We match the invalidation method parameters, so only cached entries with the same input parameters are removed from the cached.

@CacheResult()
public Customer getCustomerById(CustomerId customerId){}
@CacheRemove(typesToInvalidate = Customer.class)
public void deleteCustomer(CustomerId customerId){}
//both method have the same input parameter
//when calling deleteCustomer(CustomerId(123)) only the
//value associated to CustomerId(123) will be removed
Second Way: Partial Parameter Caching

We use partial parameter matches, so invalidation method parameters are a subset of the cached method parameters.

@CacheResult()
public Customer getCustomerByFullName(String name,String preName){}
@CacheRemove(typesToInvalidate = Customer.class)
public void deleteCustomer(String preName){}
//parameters of delete are subset of the get method
//when calling deleteCustomer("Albert") all Customers with the preName Albert will be removed
Third Way: Using Unique Identifiers
We use unique identifiers to avoid method signature collisions.
@CacheResult(uniqueIdentifier="customerId")
public Customer getCustomerById(CustomerId customerId){}
@CacheRemove(typesToInvalidate = Customer.class, uniqueIdentifier="customerId")
public void deleteCustomer(CustomerId customerId){}
//all Customer objects registered with the defined unique identifier will be removed
Least Performant Way

By not using any of the above strategies, all cached entries in a region can be invalidated and evicted, causing performance hit.

@CacheResult()
public Customer getCustomerById(String storeCode,String userId){}
@CacheRemove(typesToInvalidate = Customer.class)
public void removeCustomer(String customerName){}
//In this case all cached Customer objects will be removed from the cache.

Avoiding Cache Inconsistencies

Treat Cache Values as Immutable Objects

Modifying cached values is a bad practice that can lead to inconsistencies. Either use immutable types as values, which is the preferred way, or treat cached values as if they were immutable and create new objects on mutation.

Avoiding Method Signature Collision

Caches could have methods with the same return type and input parameters. To avoid collision, use a uniqueIdentifier with the @CacheResult annotation. The following example shows how to apply a uniqueIdentifier.
@CacheResult(uniqueIdentifier = "default-customer-address")
public Address getDefaultCustomerAddress(CustomerId customerId) {}

@CacheResult(uniqueIdentifier = "shipping-address")
public Address getShippingAddress(CustomerId customerId) {}

Only use CacheKeyVariants on Unique Identifiers

Using CacheKeyVariants is an essential rule for performance optimization.

Manual Caching

In cases where annotation caching isn't appropriate, you can cache manually. To use manual caching, inject an instance of the cacheKeyBasedRequestCache bean.

@Named("cacheKeyBasedRequestCache")
private TypedRequestCache<CacheKey> requestCache

public void method() {
String value = "cache me";
requestCache.putCacheEntry(new CacheKey(this.getClass(),"my-cache-entry", String.class), value);
//do some operations
requestCache.invalidateCacheEntries(new CacheKey(this.getClass(),"my-cache-entry", String.class))}

Logging Caching

To activate cache logging, add the following to the logback.xml located in cortex/relos/logback-config/src/main/filtered-resources/
<logger name="com.elasticpath.rest.resource.integration.epcommerce.repository" level="TRACE"/commerce-legacy/>
Cache logging dramatically decreases cache performance as it will log a lot of information.
To see the resource operation's external operation ID, add the following to the logback.xml.
<logger name="com.elasticpath.rest.cache.guava.impl" level="TRACE"/commerce-legacy/>

Configuring Request-Scoped Caching

By default, the request-scoped cache is configured to evict cached entries after 60 sec and when the number of cached entries reaches 2147483647. Parameters are configurable via the RelOS Request Cache OSGi configuration in the Apache Felix Web Console. Caches are destroyed and rebuilt when configuration values are updated through the Felix console.

Cache Metrics Monitoring

Request-scoped cache metrics are exposed as MBeans over Cortex's JMX API. The metrics Mbeans are identified by the naming pattern EP-RelOS.*.RequestCache.*.

Metrics available to monitor:
  • evictionCount – number of times entries have been evicted
  • hitCount – number of times entries have been looked up and found
  • hitRate – ratio of cache request which were hits
  • loadCount – number of times entries have been looked up
  • missCount – number of times entries have been looked up and missed
  • missRate – ratio of cache requests which weren't hits
  • requestCount – number of times entries have been looked up