The performance of the OOTB CachingStoreResolver begins to degrade when more than a handful of stores are configured in EP. This post describes a simple replacement implementation that scales much better.
The problem
The StoreResolver is used by the storefront to resolve stores by either store code or domain. The OOTB CachingStoreResolver uses a SimpleTimeoutCache to save store codes and domain-to-store mappings. The default cache timeout is 1 minute. When an entry is not found in the cache, the request is delegated to the StoreResolver to resolve the request from the database and the result is cached if valid. All this looks pretty reasonable on the surface.
The scalability problems are caused by how the cache is populated. If a store code or domain are not found in cache, then the StoreResolver reads ALL stores from the database and then returns only the matching store code. If there are 100 stores and the default cache timeout is used then we can expect cache entries to expire nearly twice every second. While this is not ideal, there wouldn't be much of an issue if reading a store were a lightweight query. However OpenJPA brings along a large number of related objects and the operation ends up being quite expensive.
It is also worth noting that a request with an invalid store code or domain will always have a cache miss and will result in fetching all stores from the database.
Two solutions
The simplest approach is to increase the cache timeout, but this does not fix the issue of handling invalid store codes or domains.
A more complete solution is to replace the CachingStoreResolver with the following class which caches all store codes or domain-to-store mappings with a single query.
import java.util.Collection;
import java.util.Map;
import com.elasticpath.sfweb.util.impl.StoreResolverImpl;
/**
* This re-implements the OOTB CachingStoreResolver to cache all stores in a single database query.
*/
public class ReplacementCachingStoreResolverImpl extends StoreResolverImpl {
/**
* Get the set of valid store codes.
*
* @return a collection of all codes for complete stores.
*/
@Override
protected Collection<String> getStoreCodes() {
final String cacheKey = "storeCodes";
Collection<String> storeCodes = (Collection<String>) getFromCache(cacheKey);
if (storeCodes == null) {
storeCodes = super.getStoreCodes();
addToCache(cacheKey, storeCodes);
}
return storeCodes;
}
/**
* Creates a map with all the store domains mapped to their store codes.
*
* @return a map populated with all store domains mapped to
* their corresponding store codes.
*/
@Override
protected Map<String, String> getDomainToStoreMapping() {
final String cacheKey = "domainToStoreMap";
Map<String, String> storeCodeMap = (Map<String, String>) getFromCache(cacheKey);
if (storeCodeMap == null) {
storeCodeMap = super.getDomainToStoreMapping();
addToCache(cacheKey, storeCodeMap);
}
return storeCodeMap;
}
private Object getFromCache(final String cacheKey) {
// TODO - Hook in your cache of choice, could be a SimpleTimeoutCache
return null;
}
private void addToCache(final String cacheKey, final Object obj) {
// TODO - Hook in your cache of choice, could be a SimpleTimeoutCache
}
}




