Limitations of out-of-the-box WebSphere Portal cache
IBM® WebSphere® Portal V4.1.x (hereafter referred to as WebSphere Portal) uses Dynacache from WebSphere Application Server as the means to cache markup fragments generated by portlets. Dynacache is configured by using XML files (servletcache.xml and dynacache.xml) and is extended with the implementation of the IdGenerator and MetadataGenerator interfaces. By creating custom generators and registering them in the servletcache.xml configuration file you can customize the generation of the cache key and cache entry details (such as expiration time).
A sample servletcache.xml configuration file is as follows:
Listing 1. Sample servletcache.xml configuration file
<?xml version="1.0" ?>
<!DOCTYPE servletCache SYSTEM "servletcache.dtd">
<servletCache>
<servlet>
<timeout seconds="900" />
<path uri="/wps/themes/html/mytheme/example.jsp" />
<idgenerator class="cxv.ExampleMarkupIDGenerator" />
<metadatagenerator class="cxv.ExampleMarkupMetadataGenerator" />
</servlet>
</servletCache>
|
Unfortunately portlets do not fit into this declarative configuration and customization scheme very well. First, while portlets may be hot-deployable, registering new generators requires a restart of the application server because these Dynacache declarative configuration files are only read on startup. Furthermore, because portlets implement the CacheableServlet interface, they are inherently incompatible with the Dynacache configuration scheme. This is because a CacheableServlet always uses its own methods to generate the cache key and cache entry details rather than using any custom generators.
Figure 1. Portlet inheritance chain
Because CacheablePortlet already implements CacheableServlet the new cache design must be built around the fact that all portlets implement CacheableServlet (all portlets ultimately inherit from CacheablePortlet). This means that Dynacache hooks into portlets by using calls to getId(:HttpServletRequest):String and getSharingPolicy(:HttpServletRequest):int. The typical method of configuring and customizing Dynacache (declaratively using the servletcache.xml) for use with portlets is not compatible with WebSphere Portal.
For an introduction to Dynacache, please refer to Joey Bernal's article Caching WebSphere Portal V4 Portlets Using WebSphere Dynamic Caching.
The default use of Dynacache by WebSphere Portal has the following limitations:
- Cache keys are not unique enough for dynamic content (request parameters dictate what gets displayed and therefore need to be incorporated into the key). WebSphere Portal provides the developer with no hooks that allow this.
- Portlets that can display both anonymous content and personalized content should use a single cache entry for the generic markup (and of course separate cache entries for each personalized fragment).
- Content expiration should be tailored to the content, not the portlet. Content expiration should be configurable on a per item basis. WebSphere Portal only allows expiration to be configured at a portlet level.
Shared cache keys use the concrete portlet ID. This means all portlet virtual instances of the same concrete portlet use the same cache entry, regardless of how each virtual instance has been configured. It also means that after an XMLAccess update of a page containing shared portlets (more on that later), any modifications will not be visible because the (now) stale cache entry will continue to be used. This is because the concrete portlet ID does not change in this situation, even if the markup generated by the updated portlet is modified.
Because the enhanced cache design must modify the cache key, it is useful to introduce a new base portlet that overrides the getId method and for the most part faithfully re-implements it. However, additional methods are called during certain parts of the ID generation process that allow the individual portlets to inject some additional details into the creation of the cache key or cache entry metadata.
The components of the original (default) WebSphere Portal cache key are shown in Figure 2. The format of the generic key differs depending upon whether or not the portlet has been configured to share content.
Listing 2. The share directive
<cache>
<expires>900</expires>
<shared>yes</shared>
</cache>
|
The share directive is specified within the portlet descriptor (portlet.xml), and the granularity is that it can be set for each concrete portlet. This means that all portlets of a certain type will have the same share configuration, regardless of whether their virtual instances are placed on public or private pages (or both).
Figure 2. Components of default WebSphere Portal cache key
Dynacache uses a full key and smaller generic key. The full key is unique to each cached item and identifies the markup fragment in the cache index. The generic key is a group identifier. Any number of cached items can share the same generic key. The generic key is used to quickly invalidate a set of cached items. As you can see from the diagram, the generic key is also used as part of the main key.
All cache entries are now tagged with the concrete portlet instance ID (CPIID) as shown in Figure 3. Including this identifier in all cache keys is necessary because it is modified whenever updates are made by using XMLAccess. The CPID used in portal shared cache keys remains unchanged after an XMLAccess update. XMLAccess updates are discussed later.
During the formation of the full cache key, an extra call to
getStateHash(:PortletRequest):String
is made. This is a new method that allows portlets to append their own details onto the end of the cache key. This is necessary if the markup generated by the portlet can be affected dynamically by request parameters. These inbound parameters represent the state for that request. The request parameters that affect the generated markup must be used to further differentiate the key of the cache entry created for this request. The result from getStateHash method is appended to the full cache key.
Figure 3. Enhanced cache key
The state hash can be used as a shorthand representation of the set of request parameters. By appending it to the end of the cache key, it allows the markup that was generated with that set of request parameters to be cached independently of markup fragments that were produced from another set of request parameters. Without the state hash, all of these markup fragments would have the same cache key. The result would be all subsequent requests after the first would simply return the same markup generated by the first request, regardless of any different request parameters. Generally, state hash need not include any user-specific information, but rather only request-specific details. If the intent is to generate a personalized response, then the share directive should be set to false so that the user identifier (user ID and session ID) will be included in that cache key.
Adding additional request state to the key is not only useful for portlets that use request parameters. For example, it can be used in portlets that display both generic and personalized content. In this case, the portlet must be configured as not shared, resulting in the inclusion of the session ID if it is available. So even if the portlet is configured with generic, shareable content, multiple cache entries (one for each request bearing a session ID) with identical contents will result. The solution is to change the portlet's cache configuration to
shared and use the getStateHash method to inject the user ID or session ID, but only when content has been personalized.
The new getStateHash method must be implemented by the portlet if you wish to append additional state onto a cache key. The call to the state hash method is done from within the modified getId method which is implemented in your new abstract portal base class. In fact, the new getId method overrides the default implementation so that it can include a call out to the getStateHash method as well as modify the cache key and enhance the cache entry metadata.
The introduction of state hash addresses the first two issues with WebSphere Portal and Dynacache. The next section deals with content expiration.
Enhancement of cache entry metadata
Cache entry metadata is represented by a FragmentInfo object within Dynacache. This FragmentInfo object is used to set the expiration time or lifespan of the cache entry. WebSphere Portal allows the specification of a cache entry lifespan in the portlet descriptor (portlet.xml). A different lifespan can be set for each portlet. Unfortunately, for portlets that display dynamic information specified by the request itself, a single fixed lifespan that applies to all requests may not be adequate. It is necessary to provide another method that allows the individual portlets to override the fixed lifespan specified in the descriptor with a custom lifespan that can be tailored for the specific request. That method is:
getExpirationTime(:PortletRequest):int
. The value returned by this method is used as the lifespan (in seconds) of the cache entry created for this request.
This mechanism allows you to finely tune the expiration for each markup fragment produced by the portlet.
Should the cache for a particular portlet need to be purged prior to the expiration time, then the last modified timestamp needs to be updated. The portlet method
getLastModified(:PortletRequest):long
is called for each request that is already cached. If the last modified timestamp is greater than the cache entry creation timestamp, the entry is removed from the cache. The getLastModified method is standard issue with Portal.
Explicit cache invalidations are prompted in a number of different scenarios, generally when a portlet acts upon an event, gets modified, or is put in a particular window state, or a non-view mode.
Implicit invalidations occur then the cache entry has reached the end of its lifespan or the Dynacache LRU algorithm purges it. You can influence this cache behaviour by setting a higher priority for certain entries, allowing them to survive in the cache for longer than a single LRU cycle. To learn more, refer to the servletcache.xml <priority> tag in the WebSphere Application Server InfoCenter.
When a portal site is updated solely by using the XMLAccess facility, all current cache entries for updated portlets can only be invalidated implicitly. These updated portlets have new concrete portlet instance IDs, which means they generate different generic keys (if using the native WebSphere Portal cache key format), but only if the portlet is configured as not shared. The new, proposed cache key format will force new keys to be created after every XMLAccess update, regardless of the portlet's shared cache configuration. Therefore these updated portlets will not use the old cache entries. However, the old cache entries will remain until they expire or are purged by LRU.
So by adding the concrete portlet instance ID to the cache key (regardless of whether the concrete portlet has been configured to share its cache entry or not), updates done with XMLAccess will be properly handled because stale cache content will no longer be used.
You can use specific requests as triggers to invalidate cache entries. This can be done declaratively in the servletcache.xml file, using the <invalidateonly> tag. See the WebSphere Application Server InfoCenter for more information.
Finally, cache invalidation can also be done programmatically.
com.ibm.websphere.servlet.cache.DynamicCacheAccessor.getCache().invalidateById(key, true);
The key parameter is simply a String. It can represent the full cache key to purge a single cached item or data ID to purge a group of a cache items. The boolean parameter true indicates that the invalidation method should not return until the invalidations have taken effect on all caches. False indicates that the invalidations will be queued for later batch processing, and the method returns immediately.
Use of Dynacache in the portal theme
Because the portal theme is driven by JSPs you can use the standard Dynacache declarative configuration to support caching of the theme. Business as usual when it comes to Dynacache. The entire theme could be cached by referring to the default.jsp URI or just parts of the theme, typically areas that are expensive to generate and can be shared by all users.
If markup of these expensive areas is inserted into the theme by using a custom tag, each custom tag must be separated into its own trivial JSP fragment in order to be addressable to Dynacache (which requires a URI).
Therefore each JSP, and therefore each markup fragment, has a URI associated with it as shown in the following excerpt from a servletcache.xml file.
Listing 3. Specifying a URI for a JSP fragment
<servlet>
<timeout seconds="0" />
<path uri="/wps/themes/html/MyTheme/expensiveArea.jsp" />
<idgenerator class="com.ibm.MyThemeExpensiveAreaIDGenerator" />
<metadatagenerator class="com.ibm.MyThemeExpensiveAreaMetadataGenerator" />
</servlet>
|
The expensive area in the example above renders different markup depending upon some criteria, such as the current page or place OID. Due to this relationship, you must create a unique cache key for each permutation of the expensive area. This is where a custom Dynacache IdGenerator comes in. The generator uses the URI as well as other specific criteria to form the cache key.
Guide to using the enhanced cache
To override portlet methods, first provide an abstract subclass of org.apache.jetspeed.portlets.AbtractPortlet. This class will override getId(:HttpServletRequest):String and supply a default implementation of getStateHash(:HttpServletRequest):String. The cache key format is illustrated in Figure 3. See the CacheControlPortlet.java class in the accompanying download.
There are a number of different methods that can require overriding depending upon the design and behaviour of the portlet. Before implementing any of the cache hook methods such as getStateHash, it is important to have an understanding of your portlet's behaviour. Portlet behaviour can be categorized as follows:
- Simple. Portlet markup is hard-coded, fixed and not configurable.
- Configurable. Portlet markup can be affected in either edit or configure mode. A user can edit the portlet to display a different message, for example.
- View Configurable. A portlet that allows input to be posted while in view mode, and this input affects the markup.
- Dependent. Portlet markup can be affected through indirect means. For example, a portlet
may poll a database or file and display any changes if the resource is updated.
This means that the markup can be modified without going through
doConfigureordoEditmethods. If changes to the resource need not be immediately reflected in the portlet, and the portlet can wait until its cache entry naturally expires, then the portlet is not considered a Dependent type, but rather a Passive Poller. - Dynamic. Portlet markup is parameter-driven, meaning it accepts request parameters that may affect the markup that is generated.
- Personalized. Portlets whose markup is customized to the particular user that made the request.
- Passive Poller. Similar to a Dependent portlet except that this portlet need not immediately refresh itself when the resource it uses to affect its look changes.
Some portlets may be described by more than one category. For example it is possible to have a personalized portlet that displays some data from a user's session but is also configurable by using edit mode and accepts request parameters that further affect its markup.
The methods that affect caching that can be implemented in a base portlet class are as follows:
-
getStateHash(:PortletRequest):String -
getLastModified(:PortletRequest):long -
getExpirationTime(:PortletRequest):int
Remember, caching is also affected by the portlet's cache configuration specified in the portlet descriptor (portlet.xml file).
The following is a set of guidelines organized by portlet categories. Which method(s) to override depends upon the category the portlet falls under.
- Cache Key
- The default cache key can be used. Because all requests for the portlet map to
the same cache key, there is no need to append additional state to the key.
Therefore the
getStateHashmethod need not be overridden. - Updates
-
These portlets cannot be updated. They effectively render static content. These
types of portlets do not need to implement the
getLastModifiedmethod because they are immutable. - Expiration
- There is no need to supply a custom expiration time by using the
getExpirationTimemethod. The markup can be cached indefinitely by configuring the portlet descriptor as follows:
Listing 4. Specifying an indefinite expiration time<cache> <expires>-1</expires> <shared>YES</shared> </cache>
- Cache Key
- Same as Simple portlets.
- Updates
- Portlets that are configured by using edit or configure mode do not need any special cache treatment. The cache fragment for the virtual portlet instance is automatically purged whenever a switch into edit or configure mode occurs.
- Expiration
- If the portlet is configured to display some item from a content service then that content item may have an expiration time associated with it. However, because that content item will be displayed (regardless of the expiration date) until the next time the portlet is configured, it makes no sense to set the expiration time of the markup fragment. Therefore configuring the portlet descriptor to cache the fragment indefinitely (as for Simple portlets) is recommended. When the portlet is configured to display a different content item, the mode switch will cause the cache entry to be purged and a new cache entry will take its place, because it will have the same cache key. If the expiration was set for the cache fragment and the entry expired before anyone modified the portlet configuration, then subsequent requests after the expiration time would not be cached and every request would need to be processed by the portlet.
- Cache Key
- Same as Simple portlets.
- Updates
- Because these types of portlets are updated within their view mode, there is
no automatic cache purge. Therefore, if the update performed in view mode affects
the markup of the portlet then the portlet author must implement the
getLastModifiedmethod. This method must return the timestamp of the update. Therefore within theactionPerformed(:ActionEvent)where the update is processed, you must set a last updated attribute with PortletData as follows:
Listing 5. Setting a last updated attribute with PortletDataPortletData data = portletRequest.getData(); data.setAttribute("LASTUPDATED", new Long((new Date()).getTime())); data.store();
Furthermore, thegetLastModifiedmethod must return this timestamp. In cases where the cache entry is older than this last modified timestamp, the cache entry is invalidated. - Expiration
- Same as Configurable portlets, except that a mode switch does not cause the cache entry to be purge, but the last modified timestamp does.
- Cache Key
- Same as Simple portlets.
- Updates
- These portlets are similar to View Configurable portlets in the sense that once a modification has been detected, the last updated attribute needs to be updated. However, the technique for detecting modifications depends upon the resource that is being monitored.
- Expiration
- Same as View Configurable portlets.
- Cache Key
- The default cache key is not adequate. Additional identifying
characteristics are needed to associate the cache entry with a particular request.
When the request includes parameters that determine how or what the portlet
should display, then those parameters or a hash value of those parameters must be included in the cache key. Overriding the
getStateHashmethod allows a portlet this ability. The result of this method is appended to the cache key. - Updates
- Because the portlet is told how or what to display on every request, the state of the portlet is transient. The portlet has no persistent state to update. Keeping track of the last update as is done with other portlets is meaningless here. However, keep in mind that a mode change will cause all the cache keys created by this portlet to be invalidated. This is no different than Configurable portlets, but in this case there is likely more than just a single cache key affected.
- Expiration
- Setting an expiration is important with these types of portlets. This is because
a mode change, which would cause an invalidation of the cache keys for this
portlet, is less likely to occur with a portlet that can accept dynamic parameters.
This is because the look and behaviour of the portlet can be changed without
modifying the configuration. Furthermore, content that the portlet is displaying
may have an expiration time associated with it. The portlet descriptor only
allows a fixed expiration time to be associated with the portlet, not with the content.
So, you must specify the expiration time on a per-content basis.
This is where portlet authors would want to implement the
getExpirationTimemethod. This method returns the number of seconds that the cache entry can live for. The Dynacache LRU algorithm may throw the cache out before then, but it certainly will not live any longer. Because this is an int value, the expiration time can be set up to 68 years into the future.
- Cache Key
- The default cache key can be used as long as the portlet descriptor is configured appropriately. The cache entry for the portlet must be configured
to not share cache fragments as follows:
Listing 6. Setting the cache entry as not shared<cache> <expires>-1</expires> <shared>NO</shared> </cache>
By setting the cache entry as not shared, the default key will include the session ID and user ID. - Updates
- If the personalized details are coming from the portlet itself (each user can apply a personal configuration in edit mode), then nothing special need be done. However, if the details are coming from some other resource, then this portlet's update behaviour would be similar to that of a Dependent portlet.
- Expiration
- Generally, personalized data does not expire but instead is updated (requiring
the implementation of the
getLastModifiedmethod). However, this need not necessarily be the case. For example, user profile modifications may not be required to be visible until the user logs on next time. In this case the user would have a new session ID, and therefore the old cache entries for that user would not be used. It is probably best to set the expiration time in the descriptor to some definite time (for example, 10 minutes).
Listing 7. Setting the expiration time for personalized data<cache> <expires>600</expires> <shared>NO</shared> </cache>
- Cache Key
- Same as Simple portlets.
- Updates
- These portlets are not concerned with keeping their cached markup synchronized with any changes to the resource that they use to display. That resource may be a database or a file that the portlet queries or parses, formats, then ultimately displays. Therefore any change in the resource will not be immediately apparent to the portlet, nor will it be reflected in the markup output by the portlet. This is why these portlets are considered passive.
- Expiration
- Because updates do not cause the portlet to refresh its cache, expiration time
must be used as the mechanism to do so. A fixed interval can be used, so configuring
the portlet descriptor is all that is needed.
Listing 8. Using the expiration time to set cache refresh interval for passive poller portlets<cache> <expires>600</expires> <shared>YES</shared> </cache>
Setting an expiration time of 10 minutes causes the portlet to poll the resource at least every ten minutes, perhaps more often, because the Dynacache LRU algorithm may purge the entry earlier.
Call sequence and frequency of cache hook methods
The methods necessary to direct cache behaviour are as follows:
-
getStateHash(:PortletRequest):String -
getLastModified(:PortletRequest):long -
getExpirationTime(:PortletRequest):int
The order of execution is as follows:
-
init() -
initConcrete() -
getId() -
getStateHash() -
getLastModified()orgetExpirationTime() -
login() -
beginPage() -
doTitle() -
service {doView(), doEdit(), doConfigure(), doHelp() } -
endPage() -
logout() -
destroyConcrete() -
destroy()
The methods below are ordered from most to least frequently called.
- The
getStateHashmethod is executed for every request that is deemed cacheable, regardless of whether the request has already been cached or not. - The
getLastModifiedmethod is executed only for requests that are deemed cacheable and a cache entry already exists for the generated cache key. - The
getExpirationTimemethod is executed only for requests that are deemed cacheable and a cache entry does not exist for the generated cache key. - If the request is not cacheable none of these methods are called.
A request is considered cacheable if all of the following conditions are met (logical AND):
- The request is a service type (for example, "perform action" is not a service type request).
- The window state is Normal, Maximized, Minimized or Detached.
- The mode is VIEW.
- The portlet cache descriptor is not 0 (the <expires> element in the
portlet.xmlfile).
This article examined the inner structure of the portlet markup fragment cache key. It also described enhancements that allow dynamic portlets (those that have their resulting markup affected by runtime request parameters) better control over the creation and destruction of cache entries. Finally, it categorized the behaviour of portlets and recommended how cache control should be handled for each category.
| Name | Size | Download method |
|---|---|---|
| CacheControlPortlet.zip | 6KB | FTP |
Information about download methods
- Download the source code used in this article.
- "
Caching WebSphere Portal V4 Portlets Using WebSphere Dynamic Caching
" (developerWorks, July 2003) provides an introduction to Dynacache.

Christian Voth is an IBM software services consultant, most recently with the WebSphere family of products, specifically WebSphere Application Server and WebSphere Portal. He has worn a variety of hats over the years, including instructor, architect, code grunt, technical advisor, and crack problem solver. He holds a computer science degree from the University of Toronto and works out of the IBM Toronto Lab, Canada.
Comments (Undergoing maintenance)





