Recently, in the web portal application I have been working on, we had a requirement to store some lookup data (such as rate revision data, state and product lists) in the memory of a servlet container (Tomcat) so we wouldn't hit the back-end database every time we access the data. At the same time, we needed to refresh the data stored in the memory at predefined time intervals so it didn't become stale and inaccurate. We also needed a mechanism to refresh different types of data stored in the memory at different time intervals. For example, rate revision data had to be refreshed once every day, whereas the lookup data could stay in the memory for longer periods of time. Object caching was the perfect solution to accomplish all of these tasks in one shot.
The best definition of object caching can be found in the functional specification document for Object Caching Service for Java (OCS4J), which says:
A server must manage information and executable objects that fall into three basic categories: objects that never change, objects that are different with every request, and everything in between. Java is well equipped to handle the first two cases but offers little help for the third. If the object never changes, we create a static object when the server is initialized. If the object is unique to every request, we create a new object each time. For everything in between, objects or information that can change and are shared across requests, between users or between processes, there is the "Object Caching Service."
Basically, object caching is all about avoiding the expensive re-acquisition of objects by not releasing the objects immediately after their use. Instead, the objects are stored in memory and are reused for any subsequent client requests. When the data is retrieved from the data source for the first time, it is temporarily stored in a memory buffer called
cache. When the same data needs to be accessed again, the object is fetched from the cache instead of acquiring it again from the data source. Finally, the cached data is released from the memory when it's no longer needed. When a specific object can be released from memory is controlled by defining a reasonable expiration time after which data stored in the object becomes invalid, from web application standpoint.
One of the main benefits of object caching is the significant improvement in application performance. In a multi-tiered application, data access is an expensive operation, compared to any other task. By keeping the frequently accessed data around and not releasing it after the first use, we can avoid the cost and time required for the reacquisition and release of the data. This results in greatly improved web application performance, since we won't be hitting the database every time we need the data.
Scalability is another benefit of using object caching. Since cached data is accessed across multiple sessions and web applications, object caching can become a big part of a scalable web application design. Object caching, just like object pooling, can help avoid the cost of acquiring and releasing the objects.
There are many advantages in using object caching in a web application, but there are a few disadvantages to caching the objects.
Any data that does not change frequently and takes longer times to retrieve from the data source is a good candidate for caching. This includes pretty much all types of lookup data, code and description lists, and common search results with paging functionality (search results can be extracted from the data source once and stored in the cache to be used when user clicks on a paging link on the results screen).
Middleware technologies such as EJB and CORBA allow the remote transfer of objects where the remote object is transferred between the client and the server. This type of access, also known as coarse-grained data access, is done to minimize the number of expensive remote method invocations. These data transfer objects (also known as value objects) can be stored in the cache if the objects don't change very frequently. This minimizes the number of times servlet container needs to access the application server every time the client needs the value objects.
How about the types of data that are not a good fit for caching? Here's a list of the data that's not recommended to be cached:
There are several object-caching frameworks (in both open source and commercial implementations) that provide distributed caching in servlet containers and application servers. Following is a list of some of the currently available caching frameworks:
If you are interested in reading more about any of these caching implementations, the resources section at the end of this article has URL links to all of these frameworks.
I looked at Commons Collections and Java Caching System API when I first started my research for a caching framework that would address all of our caching requirements. My main criteria in the initial evaluation were ease of use, future extensibility, and software cost. Both of these frameworks are open source and available from the Apache Jakarta project.
The Commons Collections API provides the object caching mechanism in the form of a Java class called
LRUMap. If you have a basic requirement for caching and don't really need any advanced, configurable caching features, then Commons Collections may not be a bad choice, but it is not the answer if you are looking for an enterprise-level caching system. If you choose Common Collections to implement object caching, you will have to manually define/configure all of the caching parameters, such as the maximum number of objects that can be cached, the maximum life of a cached object, the idle time to refresh the cache or check for "stale" objects, etc.
Java Caching System (JCS), part of the Jakarta Turbine project, is more sophisticated and extensive than Commons Collections. It's a highly flexible and configurable solution to increase overall system performance by maintaining dynamic pools of frequently used objects. As mentioned on its web site, JCS goes beyond simply caching objects in memory. It provides several important features necessary for an enterprise-level caching system:
JCS provides a framework with no point of failure, allowing for full session failover (in clustered environments), including session data across up to 256 servers. It also provides the flexibility to configure one or more data storage options, such as memory cache, disk cache, or caching the data on a remote machine.
All of these useful features in JCS made it a perfect choice for the object-caching requirement in my web portal project.
Before I started the design on my object-caching framework, I made a list of objectives that needed to be accomplished in the new framework. Following is the list of these objectives:
It is fairly simple to install and configure JCS in a web application. JCS can be installed by downloading the .zip file from the Jakarta Turbine web site, extracting the .zip file contents to a temp directory, and copying the JCS .jar file (
jcs-1.0-dev.jar) into the servlet container's common directory (I used Tomcat as the servlet container in my web application, where the common directory is
%TOMCAT_HOME%\common\lib on Windows or
$TOMCAT_HOME/common/lib on Unix-like systems). You will also need the commons-collections.jar, commons-lang.jar, and commons-logging.jar files in the web application's classpath to use JCS.
The main elements of the object caching framework are illustrated with the help of UML diagrams in Figures 1 and 2.
Figure 1. Object caching sequence diagram (Click on the screen shot to open a full-size view.)
Figure 2. Object caching class diagram (Click on the screen shot to open a full-size view.)
We configure all of the caching parameters in a properties file called cache.ccf. These parameters include caching information such as maximum number of objects that can be stored in memory, time to live (after which the cached data is automatically released from the memory), idle time (elapsed time since last access time), memory cache name (i.e., caching algorithm such as LRU or MRU), etc. In the current version of JCS, the cache properties file is in plain text format. SpiritCache framework, a commercial implementation of JCache API from SpiritSoft, supports the cache configuration in XML format.
Make sure this properties file is stored in the classpath. Note: JCS provides a method to specify a configuration file name, in case you want to use a different file to store cache properties. Check the JCS Javadocs on how to read cache configuration parameters from a file other the default properties file.
Listed below are the Java classes that a web application needs to know to use the caching functionality. These classes are located in
common.caching package in the source code provided with this article. Javadocs for these classes are also included in the source code .zip file. (The class diagram in Figure 2 shows the relationship between these Java classes.)
This is the main interface (contract) that a client program uses to handle all of the operations related to caching (i.e., storing, accessing, and releasing the data in the cache). The client program can be a JSP, Struts Action class, or a POJO (Plain Old Java Object). This interface was created to hide all of the caching implementation details from the client so if we need to switch to a different third-party caching API in the future, we wouldn't need to change any of the client code.
This is the main class in web portal caching framework. It's the base implementation of the
BaseCacheManager was created to centralize all of the cache-related methods in a single class. It's designed as a singleton to ensure that there is one and only one instance of
ICacheManager created in the servlet container's JVM. In a clustered environment where multiple web server/servlet container instances are accepting the web requests, there will be a separate
ICacheManager instance created in each JVM. If you switch to a different caching API in the future, this is the only class that needs to be modified to work with the new cache API. If you switch to a JCache-compliant caching implementation, the required changes in the cache manager should be minimal.
This interface is used to implement the actual data access logic in the web client. All of the client programs that need to use the caching mechanism must implement this interface. It has a single method called
loadCacheObject() and takes two input parameters: a String to specify the cache region name and an Object to specify the cache key. This way, the cache manager will know which client program to use (to execute the
loadCacheObject method) to reload the object in the cache when the cached data is expired after the specified "time to live" is elapsed.
ICacheKey interface was created to hide the specific logic used to create a cache key. Sometimes the cache key may not be a simple string. It may be as complex as the combination of multiple objects, and getting these values from the data source involves several lookup methods instead of one single method. In this case, the
ICacheKey interface can be used to define all of the complex logic involved in creating the cache key. This way, the cache-key creation logic will be defined in a separate class. I wrote a sample class called
TestCacheKey that implements this interface and overrides the method
getCacheKey() to give an idea about using this interface.
A cache region is defined as an organizational namespace for holding a collection of cache objects. You need to define the cache regions in the configuration file to store the data in separate memory spaces in the cache to manage controlled expiration of the cached data. Objects with similar characteristics (such as time to live and business use) should be cached in the same cache region so that they can all be invalidated at the same time, if needed. I defined separate cache regions to store the lookup data and rate revision data. To eliminate any synchronization issues that could cause poor performance, I used a separate instance of
JCS) for each cache region.
This class is used to encapsulate all of the caching statistics (such as hit count, miss count, hit rate, etc.) that can be used to monitor the effectiveness of caching each object in the web application.
I created a build script using Ant to compile all Java source code for my object-caching framework. The Ant build script, named build.xml, is located in the WEB-INF\classes directory. I also wrote a JUnit test client to test different caching scenarios using the web portal caching framework. This test script, called
CachingTestCase, is located in the WEB-INF\classes\common\caching\test directory. Extract the sample code to a new web application directory, and to test the JUnit test script, run the following commands from command line.
Change directory to the %TOMCAT_HOME%/webapps/web-app-name/WEB-INF/classes directory (in a Unix-like environment, this would be $TOMCAT_HOME/webapps/web-app-name/WEB-INF/classes).
Run the following commands:
To compile all of the Java classes included in the caching framework.
To run the JUnit test script. The test script uses Log4J API to display all of the output messages.
Follow these guidelines when you decide to cache a specific type of data in your web application. Caching should be applied carefully when other means, such as the data access itself, cannot be further improved. Caching can introduce some complexity, complicating the maintenance of the overall solution; therefore, consider the trade-off between performance and complexity before applying caching.
When considering the use of a cache, consider the expected lifetime of the objects and the refresh rate or time-to-live values associated with those objects. The cache cannot hold all of the data we would like to store, so the memory used by the cache needs to be released from time to time, either by defining a reasonable time-to-live attribute or by explicitly invalidating the cached objects when the data is no longer needed. Caching algorithms such as Least Recently Used (LRU) or Least Frequently Used (LFU) are specified so that the cache will release the object based on the frequency of access. Jack Shirazi's book Java Performance Tuning provides a very interesting discussion on the caching topic, discussing what type of data should be cached and guidelines to consider when using caching.
Note that the caching framework does not handle the creation of objects that need to be cached in a web application (i.e., the data access logic to retrieve the data from the data source is not coded in the caching classes). It relies on the client program to define the actual data-access logic. Technologies like Java Data Objects (JDO) are typically used to encapsulate the data access logic in an enterprise web application. Refer to O'Reilly's Java Data Objects to learn more on how to separate data access logic from business logic.
This article provides an overview of an object-caching framework developed for a web portal application using Jakarta's Java Caching System (JCS). The framework is very flexible and can be reused in any web application or even in a client/server Java application. This article covered the main elements of a web portal caching framework in detail and a JUnit test script to test the caching framework for various scenarios.
JCS was built as a system close to JCACHE Java Temporary Caching API (JSR-107), a description of the caching system used in Oracle 9i and other popular caching frameworks. This specification may end up as a Java extension framework in a future JDK release. One of my objectives was to keep the web portal caching framework loosely coupled with JCS. This way, if I need to switch to a different framework (such as JCache) in the future, I could accomplish the transition without major code changes in the web portal application client code.
I am currently logging (using Log4J API) cache monitoring information such as hit count, miss count, and hit rate to measure the effectiveness of caching. There may be other parameters that need to be monitored in the future to assess the impact of caching on performance. Also, to measure the response times for data access with and without using the caching, a load-testing tool such as Grinder or JMeter could be used to test scalability and performance issues.
Keeping caching in sync in a clustered environment will be a challenge, since each servlet container would have a cache manager instance in its JVM. The solution to this problem is to create a Message Driven Bean (MDB) to notify all of the cache managers when to refresh the cached data.
Conventional methods for object lookup, such as a simple hashtable, JNDI, or even EJB, provide a way to store an object in memory and perform the object lookup based on a key. But none of these methods provide any mechanism for removal of the object from memory when it's no longer needed, or automatic creation of object when it's accessed after expiration. The
HttpSession object (in the servlet package) also allows objects to be cached, but it does not have the concepts of sharing, invalidation, per object expiration, automatic loading, or spooling which are the main elements of a caching framework.
Though the integration of caching functionality into a web application does involve additional design and development effort, I think the caching benefits outweigh the extra work. I have seen a great improvement in my web application's performance, especially in accessing the lookup data and search results, after I implemented the caching framework. This web application module is currently in the testing phase. In the near future, I will post some benchmarks on the performance results (with and without using caching) to compare how effective object caching can be in designing a faster and scalable web application.
Srini Penchikala is an information systems subject matter expert at Flagstar Bank.
Return to ONJava.com.
Copyright © 2009 O'Reilly Media, Inc.