Enhancing the performance of WebSphere Commerce applications through dynamic caching

This article describes how dynamic caching configuration, in five simple steps, can significantly improve the performance of your applications on a WebSphere® Commerce site.

Share:

Darl Crick (crick@ca.ibm.com), Software Architect, IBM

Darl Crick is the Chief Architect for WebSphere Commerce Performance and Business Runtime at the IBM Toronto Software Lab, Canada.



Jennifer Schachter (jschacht@ca.ibm.com), Software Engineer , IBM

Jennifer Schachter is a Software Engineer on the WebSphere Commerce Performance team at the IBM Toronto Software Lab, Canada.



22 March 2006

Introduction

WebSphere Commerce is a resource intensive application. It requires investment both in terms of hardware to run on and the time needed to properly develop and configure an application. One way that you can maximize the value of your WebSphere Commerce project is to set up caching on your system. By investing this time to establish a good caching configuration, it is possible to improve the performance of your application by a factor of 20. If configured properly, the CPU usage on the application servers decreases significantly, and even the CPU on the database server shows reduced usage. Furthermore, once you set up the catalog pages to be cached, browsing catalog pages no longer read rows from the database. This means contention in the database for rows in the catalog tables are greatly reduced and allow for catalog updates to the system without causing performance issues.

This article equips you with the methodology to configure caching on your WebSphere Commerce site. This process provides the most effective caching on your site. In our experience, using this methodology gives you the biggest return on your invested time.

This article assumes you are familiar with the basics of caching. It is not meant to be a comprehensive resource on all things caching. There are DynaCache techniques and methods not covered in this article that are more appropriate for your specific application. For more information on these techniques, see see Tutorial: Improve WebSphere Commerce performance with dynamic caching and Caching WebSphere Commerce pages with the WebSphere Application Server dynamic cache service.


Configuring caching with WebSphere Commerce

The following five steps will walk you through the methodology we follow to configure caching for a WebSphere Commerce site.


Step 1. Caching at the servlet level

What

Servlet caching is equivalent to full-page caching. When you configure a page to be full-page cached (or servlet cached), its entry in the cache contains:

  • The servlet.
  • The content from the servlet's fragments that have no includes or forwards.

Servlet caching is encouraged when many, if not all, users can share the page. Good candidates for servlet caching are the catalog pages.

Pages containing personalized information can still be servlet cached. Instructions to do this are found in subsequent sections.

Why

Caching at the servlet level maximizes what is contained in a given cache entry, and minimizes the time required to return the cached information on its subsequent requests. For this reason, use servlet caching whenever possible.

How

To cache at the servlet level and entirely remove the execution of Java™ code and database lookups on a cache-hit:

  1. Create a cache-entry for the default WebSphere Commerce servlet.
  2. Define the cache-entry using the same parameters and pathinfo values as the servlet.
  3. Set the consume-subfragments property to true.

The following cache-entry example uses servlet caching to cache the TopCategoriesDisplay page:

<?xml version="1.0"	?>
<!DOCTYPE cache	SYSTEM "cachespec.dtd">
<cache>

	<!-- Starts Servlet -->
	<cache-entry>
		<class>servlet</class>
		<name>com.ibm.commerce.server.RequestServlet.class</name>
		<property name="consume-subfragments">true</property>
		<property name="save-attributes">false</property>
		<property name="store-cookies">false</property>
		
		<!--TopCategoriesDisplay?storeId=<storeId>&catalogId=<catalogId>-->
		<cache-id>
			<component	id="" type="pathinfo">
				<required>true</required>
				<value>/TopCategoriesDisplay</value>
			</component>
			<component	id="storeId" type="parameter">
				<required>true</required>
			</component>
			<component	id="catalogId" type="parameter">
				<required>true</required>
			</component>
		</cache-id>
	
	</cache-entry>

</cache>

Note that if your instance is struts-enabled, the name of the default WebSphere Commerce servlet changes to com.ibm.commerce.struts.ECActionServlet.class. Consult the documentation for your release to confirm the servlet name.

You can find a sample cachespec.xml file in WC/samples/dynacache/consume-subfragments. Note that this sample incorporates techniques that are not discussed yet. Be sure to update the pathinfo in the sample cachespec.xml file to match the definition of your catalog commands. Also, limit the components making up your cache-key the necessaries. For example, if you do not have a dependency on language, there is no reason for your cache-id to contain the langId component. To use the sample the cachespec.xml file, remove the extra components.


Step 2. Caching personalized fragments at the fragment level

Why

Excluding page fragments allows near servlet caching performance for pages with personalized content. The personalized fragment is cached in an entry separate from the full-page cache, and the pieces are assembled when they are retrieved from the cache. Keep in mind that the more pieces there are to assemble into a page, the longer itl takes to retrieve entries from the cache.

What

Personalized content, or content that changes depending on the circumstances, needs to be in a JSP fragment that is dynamically included by the parent page. This fragment is cached separately from the parent page.

How

To cache a fragment separately from the parent page:

  1. Add an entry to the cachespec.xml file for each fragment that is to be cached separately.
  2. Set the consume-subfragment property to false.

The following example from the sample cachespec.xml file caches the mini shopping cart separately from its parent page:

<cache-entry>
<class>servlet</class>
<name>/ConsumerDirect/include/MiniShopCartDisplay.jsp </name>
<property name="save-attributes">false</property>
<property name="store-cookies">false</property>
<property name="save-attributes">false</property>
<property name="do-not-consume">true</property>
<sharing-policy>not-shared</sharing-policy>
<cache-id>
        <timeout>3600</timeout>
            <component  id="storeId" type="parameter">
                <required>true</required>
            </component>
            <component  id="catalogId" type="parameter">
                <required>true</required>
            </component>
            <component  id="DC_userId" type="attribute">
                <required>true</required>
            </component>
 </cache-id>

The fragment being excluded must be self-executing, and cannot depend on attributes set by the parent JSP fragment. However, not all fragments in WebSphere Commerce are self-executing. To test if the fragment is self-executing, pass the fragment's URL to a Web browser, including the necessary parameters. For example, the mini shopping cart contained in the ConsumerDirect sample tore is accessed by the following URL:

http://localhost/webapp/wcs/stores/servlet/ConsumerDirect/include/MiniShopCartDisplay.jsp?storedId=10001

.

The following method describes an alternate way of determining if the fragment is self-executing:

  1. After the cachespec.xml file is configured, hit the servlet's page.
  2. Use the cachemontor to invalidate the child fragment.
  3. Execute the page request to confirm the execution of the fragment is successful. If the execution is successful, that fragment is self-executing.

The cache-entry for the parent servlet contains values for those parameters passed into the fragment using the jsp:include or c:import tags. This means that on a cache hit, the parameters passed from the parent to the fragment are not regenerated. To have new values passed in to the fragment, invalidate or re-execute the parent JSP.


Step 3. Marking self-executing fragments as uncacheable

Why

A fragment must be self-executing to be cacheable; however, not every self-executing fragment is cacheable. Instead of rendering this entire page uncacheable, you can elect to cache the full page and leave some fragments unchached. There are very few fragments that are not cacheable. Before designating something as not cacheable, determine the value of caching the fragment versus the possible changes required to make the fragment cacheable. Use this technique infrequently.

Before you decide a fragment is uncacheable, consult the different invalidation techniques available for cache content. Some of these are discussed in Step 4.

What

You can tell the WebSphere cache manager to ignore a fragment in a servlet by having that fragment make a call to the WebSphere Cache API.

How

  1. Cache the full page by following the instructions given in Step 1.
  2. Add the following call to the WebSphere Commerce cache to the top of the fragment's JSP file:
<%@page import="com.ibm.websphere.servlet.cache.*" %>
<%
((ServletCacheResponse)response).setDoNotConsume(true);
%>

Step 4. Automating the invalidation of cache entries

Why

We encourage defining invalidation rules to automate the invalidation of cache entries. If you do not have invalidation rules configured, then you need to invalidate the entire cache when content changes. This means that everything is removed from the cache and not just the changed pages. The invalidation of the complete cache is not recommended and should be avoided whenever possible.

What

The simplest way to invalidate cache entries is with time-based elements. This method is useful for user specific objects when you cannot invalidate the cache entries by any other mechanism, or when they are refreshed after a set period.

You can also invalidate entries in the cache through command-based invalidation using dependency and invalidation IDs. A cache-entry needs dependency IDs defined for each component that it relies on. When an object is cached for this entry, the dependency IDs are generated and associated to it.

These entries are invalidated using command-based invalidation rules. Command-based invalidation means invalidation rules are generated upon execution of a command. These invalidation IDs are constructed based on methods and fields provided by the commands. When a request causes invalidation IDs to be generated, the entries in the cache that have a dependency ID matching the generated invalidation ID are invalidated.

A final way to configure automatic cache invalidation is using the CACHEIVL table in combination with database triggers.

How

You can accomplish time-based invalidation by specifying the <timeout>value</timeout> sub-element within a cache-entry in the cachespec.xml file. Value is the amount of time, in seconds, the cache entry is kept in the cache. The default value for this element is 0 (zero), which indicates this entry never expires.

There is another time-based invalidation technique in WebSphere Application Server 5.1 and later. You can use the <inactivity>value</inactivity> sub-element to specify a time-to-live (TTL) value for the cache entry based on the last time the cache entry was accessed. Value is the amount of time, in seconds, to keep the cache entry in the cache after the last cache hit. The mini shopping cart should use this type of invalidation rule so that your cache does not fill up with user specific data that is no longer being accessed.

To have cache invalidation triggered by a command, you must first declare the dependency-id components for the cache-entries you want to invalidate. The dependency-id components represent the components that, should they change, would invalidate the content of this cache entry. The following sample shows the cache entry for a mini shopping cart fragment, including some of the possible dependency-id components:

<cache-entry>
		<class>servlet</class>
		<name>/ConsumerDirect/include/MiniShopCartDisplay.jsp</name>
		<property name="save-attributes">false</property>
		<property name="do-not-consume">true</property>
		<property name="store-cookies">false</property>
		<property name="consume-subfragments">true</property>

		<cache-id>
			<component id="storeId" type="parameter">
				<required>true</required>
			</component>
			<component id="DC_userId" type="attribute">
				<required>true</required>
			</component>			
			<priority>1</priority>
			<timeout>3600</timeout>	
		</cache-id>
		
		<dependency-id>storeId
			<component id="storeId" type="parameter">
				<required>true</required>
			</component>
		</dependency-id>

		<dependency-id>DC_userId
			<component id="DC_userId" type="attribute">
				<required>false</required>
			</component>
		</dependency-id>

		<dependency-id>MiniCart:storeId
			<component id="storeId" type="parameter">
				<required>true</required>
			</component>
		</dependency-id>
		
		<dependency-id>MiniCart:storeId:DC_userId
			<component id="storeId" type="parameter">
				<required>true</required>
			</component>
			<component id="DC_userId" type="attribute">
				<required>true</required>
			</component>
		</dependency-id>
	</cache-entry>

Next, the Dynamic Cache must recognize the command. You must write this command to the WebSphere Command Framework and its implementation class must extend from the CacheableCommandImpl in the com.ibm.websphere.command package. To simplify command writing for command-based invalidation, WebSphere Commerce has updated the abstract classes, ControllerCommandImpl and TaskCommandImpl, to extend from CacheableCommandImpl. Any commands that extend from these abstract classes would also extend from CacheableCommandImpl, and be eligible for command-based invalidation.

Finally, make an entry for the command in the cachespec.xml file that contains definitions for the invalidation IDs you want to generate. The following sample detects the WebSphere Commerce commands that indicate changes to an order and generates invalidation IDs to invalidate the cache entries with matching dependency IDs:

<cache-entry>
	<class>command</class>
	<sharing-policy>shared-push</sharing-policy>
	<name>com.ibm.commerce.order.commands.OrderProcessCmdImpl</name>		
	<name>com.ibm.commerce.orderitems.commands.OrderItemAddCmdImpl</name>
	<name>com.ibm.commerce.orderitems.commands.OrderItemDeleteCmdImpl</name>
	<name>com.ibm.commerce.order.commands.OrderCalculateCmdImpl</name>		
	<name>com.ibm.commerce.orderitems.commands.OrderItemUpdateCmdImpl</name>
	<name>com.ibm.commerce.orderitems.commands.OrderItemMoveCmdImpl</name>
	<name>com.ibm.commerce.order.commands.OrderCancelCmdImpl</name>
	<name>com.ibm.commerce.usermanagement.commands.UserRegistrationAddCmdImpl
	</name>	 
	<name>com.ibm.commerce.usermanagement.commands.UserRegistrationUpdateCmdImpl
	</name>
	<name>com.ibm.commerce.interestitems.commands.InterestItemAddCmdImpl</name>
	<name>com.ibm.commerce.interestitems.commands.InterestItemDeleteCmdImpl</name>
	<name>com.ibm.commerce.interestitems.commands.InterestItemListCopyCmdImpl</name>
	<name>com.ibm.commerce.interestitems.commands.InterestItemListDeleteCmdImpl</name>
	<name>com.ibm.commerce.security.commands.LogoffCmdImpl</name>
	<invalidation>MiniCart:storeId:DC_userId
		<component type="method" id="getCommandContext">
			<method>getStoreId</method>
			<required>true</required>
		</component>
		<component type="method" id="getCommandContext">
			<method>getUserId</method>
			<required>true</required>
		</component>
	</invalidation>
		
</cache-entry>

Note: To invalidate, the mini cart must have dependency IDs that match the invalidation IDs being generated. The sample mini cart entry is invalidated in this case.

A final way to invalidate cache content is by making entries to the CACHEIVL table. To change the frequency of the DynaCacheInvalidation command, refer to the section on "scheduler" in the WebSphere Commerce Administration Guide. The WebSphere Commerce scheduler runs a DynaCacheInvalidation command at a set interval. This command processes the entries in the CACHEIVL table as follows:

  • The clearall string value in the TEMPLATE or DATA_ID columns of the CACHEIVL table is used by DynaCacheInvalidation to clear the cache by dynamic cache invalidation API (clear).
  • If the TEMPLATE column is set, then the DynaCacheInvalidation command calls the dynamic cache invalidation API (invalidateByTemplate) and uses the name as the template ID. If the clearall string value (which is case insensitive) is found in the TEMPLATE column, then the DATA_ID column is ignored and the DynaCacheInvalidation command will clear the cache. If the TEMPLATE column is not empty, the command invalidates by the template ID, ignoring the DATA_ID column.
  • If the DATA_ID column is set and the template name is not set, then the DynaCacheInvalidation command calls the dynamic cache invalidation API (invalidateById) and uses the DATA_ID as the ID. If the TEMPLATE column is empty and the clearall string value is found in the DATA_ID column, then the command clears the cache.
  • When the dynamic cache invalidation API is called, it invalidates the cache entries.

We suggest creating database triggers to populate the CACHEIVL table. You can use the following sample triggers to detect and react to changes in the store and catalog pages. This list is only a subset of several possible triggers, and is not comprehensive. Again, ensure that the invalidation IDs have matching dependency-id values on the cache entries you want them to invalidate.

------------------------------Catalog & Store Triggers------------------------------

CREATE TRIGGER cache_1
AFTER UPDATE ON catalog
REFERENCING OLD AS N FOR EACH ROW MODE DB2SQL
INSERT INTO cacheivl (template, dataid, inserttime)
(SELECT NULLIF('A', 'A'),  'storeId:' ||  RTRIM(CHAR(storecat.storeent_id)), CURRENT TIMESTAMP
   FROM storecat
  WHERE storecat.catalog_id = N.catalog_id);

CREATE TRIGGER cache_2
AFTER UPDATE ON storecat
REFERENCING NEW AS N FOR EACH ROW MODE DB2SQL
INSERT INTO cacheivl (template, dataid, inserttime)
(SELECT NULLIF('A', 'A'),  'storeId:' || RTRIM(CHAR(N.storeent_id)), CURRENT TIMESTAMP
   FROM catalog
  WHERE catalog.catalog_id = N.catalog_id);

CREATE TRIGGER cache_3
AFTER UPDATE ON cattogrp
REFERENCING NEW AS N FOR EACH ROW MODE DB2SQL
INSERT INTO cacheivl (template, dataid, inserttime)
(SELECT NULLIF('A', 'A'),  'storeId:' || RTRIM(CHAR(storecat.storeent_id)), CURRENT TIMESTAMP
   FROM storecat
  WHERE storecat.catalog_id = N.catalog_id);

CREATE TRIGGER cache_4
AFTER UPDATE ON staddress
REFERENCING NEW AS N FOR EACH ROW MODE DB2SQL
INSERT INTO cacheivl (template, dataid, inserttime)
(SELECT NULLIF('A', 'A'),  'storeId:' || RTRIM(CHAR(storeentds.storeent_id)), CURRENT TIMESTAMP
   FROM storeentds
  WHERE storeentds.staddress_id_cont = N.staddress_id);

You are not restricted to one of the invalidation techniques mentioned. Your cache invalidation setup can combine any or all of them to ensure that you can achieve the necessary invalidation for your system.


Step 5. Replicating the cache in a clustered environment

Why

In a production environment, it is common to have several application servers in a cluster. Certain cache entries are highly reusable across users, and shared between servers in a cluster. Configuring cache replication allows you to accomplish this. Furthermore, cache replication is necessary to ensure that invalidation messages are shared between the servers in a cluster.

Sharing cache content creates some network traffic. To minimize the impact, only replicate valuable content.

What

To replicate cache content, identify which of the cache entries is to be shared between the servers. These types of objects are likely the content cached at the servlet level, since that generally does not contain personalized data. These entries are a good starting point. Do not replicate any cached JSP fragment that does not have a long life, or is not useable across users, unless the cost to create them is very high.

You will pick the sharing policy for each cache entry. This sharing policy defines how data is replicated from server to server. You also need to configure replication in your environment by creating a Replication Domain, Replicator Entries, and then enabling the cache replication on each of the servers.

How

Once you have identified which entries in the cache you wish to replicate, add an element to their cache-entries: <sharing-policy>Value<sharing-policy>. The following table defines all possible value variables.

Table 1. Value variables
ValueDescription
not-sharedCache entries for this object are not shared among different application servers. These entries can contain non-serializable data. For example, a cached servlet can place non-serializable objects into the request attributes, if the <class> type supports it.
shared-pushCache entries for this object are automatically distributed to the dynamic caches in other application servers or cooperating Java Virtual Machines (JVMs). Each cache has a copy of the entry at the time it is created. These entries cannot store non-serializable data.
shared-pullCache entries for this object are shared between application servers on demand. If an application server gets a cache miss for this object, it queries the cooperating application servers to see if they have the object. If no application server has a cached copy of the object, the original application server runs the request and generates the object. These entries cannot store non-serializable data. This mode of sharing is not recommended.
shared-push-pullCache entries for this object are shared between application servers on demand. When an application server generates a cache entry, it broadcasts the cache ID of the created entry to all cooperating application servers. Each server then knows whether an entry exists for any given cache ID. On a given request for that entry, the application server knows whether to generate the entry or pull it from somewhere else. These entries cannot store non-serializable data.

We recommend using shared-push for content that is highly reusable across users and is expensive to regenerate. The default policy assumed for unmarked entries is configured in the WebSphere Administrator Console. We advise defining this as PUSH. We also suggest explicitly marking the sharing policy for each entry in the cachespec.xml file, even if it matches the default, to avoid confusion.

Do not replicate all of your cached data. At a minimum, replicate servlet cached catalog pages and invalidation messages.

Remember that changes in one node's cachespec.xml will not appear on the other nodes automatically. You must copy the cachespec.xml manually between the servers.

At this point, ensure that the environment itself is set up to allow replication. An Internal Replication Domain must exist for the cluster, and Replicator Entries are needed for the different servers. You can find detailed configuration instructions in Tutorial: Improve WebSphere Commerce performance with dynamic caching.


Conclusion

The WebSphere Commerce Performance Team has developed a methodology that we follow to configure caching for a WebSphere Commerce site. In our experience, these five steps are key to achieving optimal performance. At each step, we explained what motivation lies behind it, and how you, too, can accomplish what we have set out to do.

Resources

Learn

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=106393
ArticleTitle=Enhancing the performance of WebSphere Commerce applications through dynamic caching
publish-date=03222006