Level: Introductory Darl Crick (crick@ca.ibm.com), Software Architect, IBM Jennifer Schachter (jschacht@ca.ibm.com), Software Engineer , IBM
22 Mar 2006 This article describes how dynamic caching configuration, in five simple steps, can significantly improve the performance of your applications on a WebSphere® Commerce site.
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:
- Create a cache-entry for the default WebSphere Commerce servlet.
- Define the cache-entry using the same parameters and pathinfo values as the servlet.
- 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:
- Add an entry to the cachespec.xml file for each fragment that is to be cached separately.
- 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:
- After the cachespec.xml file is configured, hit the servlet's page.
- Use the cachemontor to invalidate the child fragment.
- 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
- Cache the full page by following the instructions given in Step 1.
- 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
| Value | Description |
|---|
| not-shared | Cache 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-push | Cache 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-pull | Cache 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-pull | Cache 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
About the authors  | |  | Darl Crick is the Chief Architect for WebSphere Commerce Performance and Business Runtime at the IBM Toronto Software Lab, Canada.
|
 | |  | Jennifer Schachter is a Software Engineer on the WebSphere Commerce Performance team at the IBM Toronto Software Lab, Canada.
|
Rate this page
|