Cortex Caching
Cortex can cache domain objects it retrieves from Elastic Path Commerce Engine to improve performance on requests which retrieve the same domain object multiple times. Cortex uses a technique called request-scoped caching to do this.
Domain Objects and Caching
Domain objects are classes that represent a unique set of data. Typically in Cortex, domain objects are carts, shoppers, products, and so on.
Domain objects are used in nearly all Cortex operations: getting products, logging in customers, adding to cart, and so on. Most domain objects are stored within, or require calls to Commerce Engine’s database, which makes retrieval of these objects expensive operations. Multiple retrievals of the same domain objects within a single Cortex request, without proper caching, can lead to a significant decrease in performance.
How Domain Objects are Cached
Domain objects are cached at the repository level when a repository method retrieves them. Repository methods are annotated with @CacheResultand
and @CacheRemove
annotations which cache an object and evict an object from the cache as needed. Additionally, cache key variants allow for the same cached object to be retrieved by different repository methods using a different key.
Best Practices
When to Cache
Caching should be used only when needed, as caching all methods within a repository leads to cache pollution and duplicate entries.
In general, cache a method response in the following situations:
If the method calls Commerce Engine services
Any methods that call down to Commerce Engine to retrieve a domain object should be cached, as this typically requires calling down into the database and is thus an expensive call.
If the method calls external services
Calls to external services can be slow and have an actual monetary cost, so caching the response saves processing time and often money.
If multiple methods require the same domain object
Occasionally, a repository has multiple methods which call down to Commerce Engine for the same object. Rather than caching each method’s response, extract the service calls to a single helper method and cache the response of the helper method
Methods that call to other repository methods or that extract information from domain objects do not need to be cached.
Caches 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.
Cortex Caching Prerequisites
To enabling caching, ensure your repository class meets the following prerequisites.
@Component
Repositories
Most repositories use the @Component
annotation. To enable request-scoped caching on repositories, ensure the following prerequisites are met:
Ensure that your repository is located in package following the naming pattern:
*.integration.epcommerce.repository
, where*
is any prefixFor example,
com.elasticpath.rest.resource.integration.epcommerce.repository
would be an appropriate package.Ensure that your repository’s class name ends with
RepositoryImpl
such as,CarsRepositoryImpl
.
Cortex caches on runtime for @Component
annotated repositories.
@Named
Legacy Repositories
Legacy repositories using the @Named
annotation require the steps described for @Component
annotated repositories, as well as the following requirements:
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"/>
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"/>
Cortex caches at load time for @Named
annotated repositories.
Enabling Request-Scoped Caching on a Repository Method
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.
Cache Key Variants
Repository methods may need to retrieve the same domain objects by different keys, as different resources provide different information on the same object. For example, a resource which knows about a user’s default cart may not know about that cart’s GUID, whereas a resource accessed via a URI that retrieves a specific cart does.
If you must retrieve the same domain object with via different unique identifiers, enable cache key variants to avoid a decrease in performance.
How Cache Key Variants Work
Domain objects may have multiple key variants by which they can be retrieved. In general, any uniquely identifying field of a domain object can be used as a cache key.
Cache key variants allow the domain object to register itself with a CacheKeyManager
class, tracking all possible values that useable as a cache key. This allows Cortex to identify when an object can be retrieved from the cache, even if it is called by a different key than it was initially cached under.
For example, the class Customer has two fields which can be used as unique identifiers:
class Customer {
String storeCode, userId;
String guid;
}
A repository class has the following cached methods, both of which retrieve the same domain object by different unique identifiers in their method signatures:
@CacheResult
public Customer findCustomerByUserId(String storeCode, String userId) { ... }
@CacheResult
public Customer findCustomerByGuid(String guid) { ... }
In this example, the first method, findCustomerByUserId()
, caches the domain object. The second method, findCustomerByGuid()
, utilizes cache key variants to retrieve the domain object cached by findCustomerByUserId()
, rather than retrieving the object again from Commerce Engine.
Implementing Cache Key Variants
Cache key variants are not implemented by default. To implement cache key variants on a domain object:
- Implement the
CacheKeyVariants
interface - Within this implementation, register the key variants of the domain object you wish to cache
- Register the interface implementation with the
CacheKeyManager
class
Key Variants Must be Unique
Key variants have to be unique otherwise inconsistencies might occur.
For example, the CustomerCacheKeyVariants
class registers the key variants of the Customer
domain object:
@Singleton
@Named("customerCacheKeyVariants")
public final class CustomerCacheKeyVariants implements CacheKeyVariants<Customer> {
@Override
public Collection<Object[]> get(final Customer customer) {
return Arrays.asList(
new Object[] { customer.getGuid() },
new Object[] { customer.getStoreCode(), customer.getUserId() }
);
}
@Override
public Class<Customer> getType() {
return Customer.class;
}
}
Best Practices
Avoid Method Signature Collision
Caches may 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) { ... }
CacheKeyVariants
on Unique Identifiers
Only use Using CacheKeyVariants
is an essential rule for performance optimization.
Configuring Request-Scoped Caching
Configuring Cache Eviction
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.
Logging Caching
To activate cache logging, add the following to the logback.xml
file located in the cortex/relos/logback-config/src/main/filtered-resources/
directory.
<logger name="com.elasticpath.rest.resource.integration.epcommerce.repository" level="TRACE"/>
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 logback.xml
:
<logger name="com.elasticpath.rest.cache.guava.impl" level="TRACE"/>
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 evictedhitCount
– number of times entries have been looked up and foundhitRate
– ratio of cache request which were hitsloadCount
– number of times entries have been looked upmissCount
– number of times entries have been looked up and missedmissRate
– ratio of cache requests which weren’t hitsrequestCount
– number of times entries have been looked up