Caching in Salesforce Commerce Cloud — Part 1

Salesforce Architects
Salesforce Architects
11 min readFeb 18, 2021

--

Introduction

This is Part 1 of a two-part series on caching strategies used in Salesforce Commerce Cloud implementations. Though the caching strategies covered here are common for most customer experience platforms, this series is focused specifically on Salesforce Commerce Cloud offerings with the intent of sharing learnings, experiences, and best practices from the Salesforce Commerce Cloud architect community.

Digital space is evolving with growing customer expectations. The goal of virtually every merchant is to have a fast and reliable website as a foundation to deliver best-in-class consumer experiences. As internet traffic and data volume grows, appropriate caching strategies are essential to achieve customer-expected speed and platform efficiency.

In a nutshell, caching brings content delivery as close to the consumer as possible and reduces long loops of system execution and data processing to deliver great digital experiences.

There is a common misconception that caching in Salesforce Commerce Cloud means “adding <iscache> to a few templates”, but there is much more to it than that. Let’s start with the different layers of caching for a typical B2C use case:

  • The shopper’s browser
  • The CDN
  • The web server
  • The application server
  • The storage layer (e.g., the database)
Diagram

A good caching strategy strives to move the cached content as close to the user as possible; the more layers need to be traversed, the slower the response times and the lower the potential throughput of the website.

Example use case

Let’s look at an example that illustrates how a good caching strategy looks. Often, developers work in silos or have a limited understanding of all available architectural layers (while having a very good understanding of one or two of the layers). This leads to solutions that are limited in their scalability. The following example illustrates the power of using a combination of layers instead of trying to solve the problem at a single layer.

Requirement: For each product listed in a search result, show a heart icon that is filled (or colored) for those items that are saved to the consumer’s wishlist.

A typical solution
A common approach to this type of requirement is that the backend developer will implement a solution that renders HTML representing the wishlist state of a given product. This is functionally correct but problematic as the result is specific to each user and thus the application needs to dynamically calculate this information for each product tile (i.e. the little product area shown in search results) traversing all the architectural layers.

A better solution
With a closer look at the requirement, it becomes clear that the wishlist contents do not change frequently and that for scalability reasons it would be desirable to have product tiles cached (without any dynamic calculations). Having this in mind, a better solution would expose the wishlist content as a HTML data attribute in a pre-existing dynamic include (e.g., customer information or login state in the header) and then update the heart icon status via client-side JavaScript. On the server side, this approach comes at almost no additional cost and the operation on the client is relatively cheap as well.

This solution makes use of the available layers in the order that best achieves great end-consumer performance while supporting much more scale compared to the typical (server-side) solution.

Caching in Salesforce Commerce Cloud

Now that you’ve seen an example of caching fundamentals at work, let’s focus on some of the specifics in Salesforce Commerce Cloud. The three architectural layers that support caching are:

  • Web server layer → Page cache
  • Application server layer → Custom caches (CacheMgr)
  • Storage layer → Custom attributes or custom objects

Each layer is applicable to different circumstances and each comes with set up considerations to bear in mind as you implement it.

Page caching

Page caching is among the most critical concepts of the cache mix as it represents the topmost layer of the platform; any request served from the page cache will not create load on the application or storage layers.

So called Remote Includes make this concept even more powerful; these server-side includes (SSI) are placeholders in the page for which the web tier will issue a new request that can have its own cache policy. This is best explained by example. Consider a static content page that is entirely cacheable, but on which we want to include dynamic information like the customer’s login state. A remote include enables us to do this. While the main request might be cached for a number of days, a different request is issued that will dynamically retrieve the customer information to display in the header. This (sub-)request will not be cached. The web tier will then insert the dynamically computed piece of the page into the cached frame and the result is a complete page that has both cached and uncached elements on it.

The page cache is in essence a key-value store; the key is the full URL including query string and the value is the cached page. The page may contain markers for remote includes that are resolved the same way. This diagram illustrates the high-level processing logic.

Page cache diagram

Example of different URLs
https://www.domain.com/path?param1=1 https://www.domain.com/path?param1=2 (change in parameter)
https://www.domain.com/path?param1=1&param2=abc (additional parameter)
https://www.domain.com/path?param2=abc&param1=1 (change in parameter order)
https://www.domain.de/path?param1=1 (different domain)
https://www.domain.com/otherpath?param1=1 (different path)

The above URLs will all create different cache entries and may (inadvertently) contribute to excessively high amounts of cache entries and a lower cache hit rate, as all those results need to be computed first. That’s why it is important to avoid adding dynamic parameters to includes that are likely to change frequently (but do not impact the response). One commonly applied anti-pattern is adding a position parameter to product tiles in a search result, which leads to very ineffective tile caching. This is because each tile is being cached numerous times depending on its position in the search result while always displaying the exact same product. This is often done to allow navigating the search result on the product detail page, which is achieved by passing the search information via the product detail page URL. The use case can be achieved by applying a client-side solution instead, which will significantly increase the throughput of the search pages.

There may be parameters appended to your URLs that have no meaning for your page, such as the campaign ID of a newsletter campaign. Salesforce Commerce Cloud has a new feature to ignore defined URL parameters for efficient page caching. With this feature you can ignore certain parameters for the purpose of the caching. To configure these parameters, select Administration > Feature Switches. The same section enables you to cache 404 responses, which will improve your site’s scalability as well.

Personalized caching
Salesforce Commerce Cloud also offers an out-of-the-box solution to personalize caching. Personalized caching means that the cache key will be amended by the currently active price books and applicable promotions.

Let’s look at what that would mean for two customers looking at the same page (i.e., the exact same URL). For a case in which customer A has price book X registered and customer B has price book Y registered, the same product (no change in URL) will be cached twice. All customers with price book X registered will then subsequently be served with the respective cache entry, as would customers with price book Y. As you can see, depending on the number of price books and promotions available, this may lead to lots of cache entries (regardless of the actual price of the product being different) and thus should only be used where necessary.

Implementation
There are two APIs to control the caching behavior of the response.

  1. The dw.system.Response#setExpires(milliseconds) script API. See the documentation for more details.
  2. The <ISCACHE> tag.

Note: The Storefront Reference Architecture (SFRA) does provide decorators that can be used instead of setExpires. The decorators will apply preconfigured caching times.

Historically, the ISML tag (<ISCACHE>) was the only option and the script API was introduced at a later stage. We strongly recommended that you discontinue use of the ISML tag and use the script API instead. Both approaches control the caching of the entire response, not individual templates, and thus have the identical effect. ISCACHE can be confusing, however, as it may suggest you are just caching a template (which, again, is not the case). Even if that is understood by the developers, it is often hard to understand which template defines the caching behavior of a response as they can be nested and each template can have its own ISCACHE tag (if this is the case the lowest defined cache time will be applied). Additionally, a template might be used in different contexts that require different caching policies, which would make it even more complex. By simply using the scripting API and defining the caching behavior in the controller all those challenges essentially go away.

The following code shows an example of using the script API.

Application level caching

Some endpoints (entire pages or remote includes) need to serve dynamic information (such as consumer data or cart information) and thus page caching cannot be applied. Those pages may incorporate the results of expensive calculations that could be cached to reduce the overall processing time of the dynamic request. A prominent example is “configuration as code”. This is typically (but not exclusively) used by customers with many brands and countries. Configuration is stored in JSON files that can then be extended by brand- and country-specific overrides. While reading a JSON file and merging JavaScript objects is not an expensive operation as such, given the frequency of this operation it can consume a quite significant amount of processing time. By caching the configuration for each brand/country combination this recalculation across every request (and every remote include) can be avoided, resulting in faster page rendering times on the site.

You have several different options for caching information at the application layer.

Request caching
If information needs to be saved within a single request, data can be either stored inside a module, which will keep its state in case it is required again. This is a great way to save data in the request with additional logic attached.

Request caching can be useful if you want to save the customer’s selected store but don’t want developers to directly interact with it. The module can then save the store ID internally and only expose methods to return the store object itself or to perform actions with the store while hiding implementation specifics (and keeping them in a single central place).

If the use case is just to store a small piece of data, request.custom can be used to save and read the information.

Custom objects
Custom objects are very versatile: They can be imported and exported, they can be both written and read, and they are persisted and thus consistent across all application servers. Typical use cases here include scenarios where the cached data must not get lost. A common use case is to act as intermediate storage for data that will be processed later. The downside of custom objects is that they are stored in the database and thus all architectural tiers need to be traversed.

Custom caches
Custom caches enable developers to store a limited amount of information. They are not shared across application servers thus the data is not synchronized. Therefore they are best used to save intermediate results of expensive operations that need to be performed within dynamic requests or operations that happen very frequently.

Files
File caches are great for build-time optimizations, for example, if templates need to be generated (in scenarios where template languages other than ISML are used). They are also useful where development code can be optimized or environment-specific configurations need to be created.

Session
If you need to cache smaller pieces of information for a given user, the session might be your best option. It is easy to implement and the data can be used for building dynamic customer groups. There are two ways to store data in the session:

session.custom

  • Use this when data needs to be used for building dynamic customer groups.
  • When data is stored in session.custom the platform will update the dynamic customer groups. This can be expensive, so use this with care.

session.privacy

  • Use this when data does not need to be used for building dynamic customer groups.
  • Does not trigger customer group update and thus consumes less resources.
  • Gets cleared after logout.

Static content caching

A slightly different form of caching is static content caching, which refers to images, stylesheets, and client-side JavaScript — any type of file that is directly downloaded and consumed by the browser. Those files are delivered by the origin server and are then cached within the eCDN.

Static files are either managed directly in the environment or can be included in the code. For the latter, they are often generated during the build process. Once those sources are on the server, they are served as-is and cannot contain any dynamically calculated information; thus they are static.

Rest API (OCAPI) caching

Salesforce Commerce Cloud also provides the capability to manage OCAPI caching declaratively through OCAPI configuration. This caching is managed by the application controller layer. You can configure OCAPI settings in Business Manager (Administration > Site Development > Open Commerce API Settings). These cache settings can be configured at each API level individually.

Replication and cache clears

All caches covered thus far can be cleared or flushed. This is important to understand for two reasons. First, you want to ensure that data is not cached longer than desired, and second you want to avoid frequent cache clears, which will negatively impact performance and scalability.

Summary

Now that you have a better understanding of caching strategies for Salesforce Commerce Cloud, check out Part 2 of this series, which covers techniques for measuring and optimizing the performance of your caching strategies.

About the authors

Danny Gehl is a Success Architect at Salesforce. Danny has been working in the digital space for the last 16+ years and is one of the most experienced architects in this space at Salesforce. Danny is well known in the Salesforce Commerce Cloud customer and partner community, has been supporting the Commerce Cloud community since 2008, and has delivered some of the largest implementations of Salesforce Commerce Cloud.

Neeraj Yadav is a Success Architect at Salesforce. Neeraj has spent most of his career delivering digital transformation programs around the world. Neeraj joined Salesforce Commerce Cloud (previously Demandware) in 2015 after spending 12 years in product development and digital platform delivery. Neeraj’s areas of interest include system integration and product development.

--

--

Salesforce Architects
Salesforce Architects

We exist to empower, inspire and connect the best folks around: Salesforce Architects.