Web applications are typically accessed by many concurrent users. Usually, the application's data is stored in a relational database or filesystem, and it takes time and costs overhead to access these data sources. Database-access bottlenecks can slow down or even crash the application if it receives too many simultaneous requests. Object caching is one technique that overcomes this problem. In this article, Srini Penchikala discusses a simple caching implementation framework he created to cache the lookup data objects in a Web portal project.
Object caching allows applications to share objects across requests and users, and coordinates the objects' life cycles across processes. By storing frequently accessed or expensive-to-create objects in memory, object caching eliminates the need to repeatedly create and load data. It avoids the expensive reacquisition of objects by not releasing the objects immediately after their use. Instead, the objects are stored in memory and reused for any subsequent client requests.
Here's how caching works: When the data is retrieved from the data source for the first time, it is temporarily stored in a memory buffer called a cache. When the same data must be accessed again, the object is fetched from the cache instead of the data source. The cached data is released from memory when it's no longer needed. To control when a specific object can be released from memory, a reasonable expiration time must be defined, after which, data stored in the object becomes invalid from a Web application's standpoint.
Now that we have covered the basics of how caching works, let's look at some of the well-known scenarios in a J2EE application that use object storage mechanisms similar to caching.
Conventional methods for object lookup such as a simple hashtable, JNDI (Java Naming and Directory Interface), or even EJB (Enterprise JavaBeans) 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 either removing the object from memory when it's no longer needed or automatically creating the object when it's accessed after expiration. The
HttpSession object (in the servlet package) also allows objects to be cached, but lacks the concepts of sharing, invalidation, per-object expiration, automatic loading, or spooling, which are the essential elements of a caching framework.
Object caching in Web portals
A portal must manage both user profiles and the objects available at the portal. Since most Web portals provide the single sign-on (SSO) feature, storing the user profile data is critical even if the user switches between various modules in the Web portal application. The user profiles should be securely stored in the cache so other Web users cannot access them. The objects can be aged out of the cache to free up space or the idle-time feature can remove objects not being accessed. This simplifies object management, as the application doesn't need to constantly monitor which objects are in demand at any given time. The "hot" objects are automatically available in the cache. Objects that are expensive to create or fetch can be written to a local disk and transparently retrieved as needed. Thus, object caching may be used for managing the user profile information and lookup data, such as company product information, that can be shared among multiple portal users.
Object caching benefits and liabilities
One of the main benefits of object caching is the significant improvement in application performance. In a multitiered application, data access is an expensive operation compared to other tasks. By keeping frequently accessed data and not releasing it after its first use, we can avoid the cost and time required for the data's reacquisition and release. Object caching results in improved Web application performance because of the following reasons:
- It reduces number of trips to the database or other data sources, such as XML databases or ERP (enterprise resource planning) legacy systems
- It avoids the cost of repeatedly recreating objects
- It shares objects between threads in a process and between processes
- It efficiently uses process resources
Scalability is another benefit of 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's design. Object caching helps avoid the cost of acquiring and releasing objects. It frees up valuable system hardware and software resources by distributing data across an enterprise rather than storing it in one centralized place such as the data tier. Locally stored data directly addresses latency, reduces operating costs, and eliminates bottlenecks. Caching facilitates management of Web applications by allowing them to scale at peak traffic times without the cost of additional servers. It can effectively smooth performance curves in a Web application for all-around better performance and resource allocation.
Object caching also includes a few disadvantages, such as memory size, for example. The cache may consume significant heap space in the application server. JVM memory size can become unacceptably huge if a lot of unused data is in the cache and not released from memory at regular intervals.
Another disadvantage is synchronization complexity. Depending on the kind of data, complexity increases because consistency between the cached data's state and the data source's original data must be ensured. Otherwise, the cached data can fall out of sync with the actual data, which leads to data inaccuracies.
Finally, changes to the cached data can vanish when the server crashes, another disadvantage. A synchronized cache could prevent this problem.
Typical uses of object caching include storing HTML pages, database query results, or any information that can be stored as a Java object. Basically, any data that does not frequently change and requires a significant amount of time to return from the data source is a good candidate for caching. That includes most 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 for use when the user clicks on the results screen's paging link).
HttpSession object in the Tomcat servlet container offers a good example of object caching. Tomcat uses an instance of
Hashtable to store session objects and expire stale session objects using a background thread.
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, minimizes 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 frequently, which limits the number of times the servlet container must access the application server.
More examples of object-caching uses follow:
- Enterprise JavaBeans: EJB entity beans represent database information in the middle tier, the application server. Once created, the entity beans are cached in the EJB container, which avoids expensive data retrieval (resource acquisition) from the database.
EJBHomeFactorycache: If client applications don't cache the stub somewhere, then remote method invocation can become much more expensive because every logical call to the server requires two remote calls: one to the naming service to fetch a stub and one to the actual server. This problem can be solved by creating an
EJBHomeFactoryclass to cache the references to EJB
Homeinterfaces and reusing them for the subsequent calls.
- Web browsers: Most popular Web browsers such as Netscape and Internet Explorer cache frequently accessed Webpages. If a user accesses the same page, the browsers fetch the page's contents from the cache, thus avoiding the expensive retrieval of the contents from the Website. Timestamps determine how long to maintain the pages in the cache and when to evict them.
- Data cache: Data stored in a RDBMS (relational database management system) is viewed as a resource that is sometimes hard to acquire. A correctly sized cache is a crucial component of a well-tuned database. Most databases incorporate a data cache of some sort. Oracle, for example, includes a shared global area that contains a cache of recently used database blocks and caches of compiled stored procedure code, parsed SQL statements, data dictionary information, and more.
How about data not fit for caching? Here's a list of data not recommended for caching:
- Secure information that other users can access on a Website
- Personal information, such as Social Security Number and credit card details
- Business information that changes frequently and causes problems if not up-to-date and accurate
- Session-specific data that may not be intended for access by other users
Resources stored in the cache require memory. If these resources are not used for a long time, holding on to them proves inefficient. Because the cache's capacity is limited, when the cache is full, we must purge some of the cache content before filling it again. An application can explicitly invalidate cached objects in three different ways: by associating a "time-to-live" (TTL) or "idle-time" with an object, or if the caching system's capacity has been reached (this is a configurable value), objects not recently used will be removed by the caching system.
A variety of cache expiration mechanisms can remove objects from a cache. These algorithms are based on criteria such as least frequently used (LFU), least recently used (LRU), most recently used (MRU), first in first out (FIFO), last access time, and object size. Each algorithm has advantages and disadvantages. LFU and LRU are simple, but they don't consider the object size. A size-based algorithm removes big objects (that require much memory), but the byte-hit rate will be low. It's important to consider all the Web application's requirements before deciding which cache algorithm to use for expiring cached objects.
Object caching in a J2EE application
In distributed system such as a J2EE application, two forms of caching can exist: client-side and server-side caching. Client-side caching is useful for saving the network bandwidth and the time required to repeatedly transmit server data to the client. On the other hand, server-side caching is useful when many client requests lead to repeated acquisitions of the same resource in the server. Server-side caching can be achieved in any tier, i.e., database, application server, servlet container, and Web server.
Server subsystems such as the servlet engine can improve server performance by pooling such items as request, response, and buffer objects. The servlet objects themselves can be stored in the cache. The group invalidation feature can then be used when application reload is required. All servlets and related objects within an application can be cleaned up with a single method call. Part or all of a response can be cached if it is applicable to more than one response, which can significantly improve response time. Similarly, in the data tier, caching can provide a significant performance improvement.
IronEye Cache (from IronGrid) provides the option of storing frequently requested SQL statements in a cache to minimize database calls and deliver commonly requested information quickly. Oracle provides object caching in all tiers. Oracle Web Cache sits in front of the application servers (Web servers), caching their content and providing that content to Web browsers that request it. Object Caching Service for Java provides caching for expensive or frequently used Java objects within Java programs. The Object Caching Service for Java automatically loads and updates objects as specified by the Java application. And finally, Oracle iCache Data Source provides data caching within the database server.
Object caching in a J2EE cluster
Object caching in a cluster is important because multiple JVMs run in a cluster, and keeping all the cluster members' cached data in sync is crucial. Since each servlet container has a cache manager instance in its JVM, data changes must be reflected in all caches to prevent stale reads. This can be achieved by using a message-driven bean (MDB) to notify all of the cache managers when to refresh the cached data. Many caching frameworks provide built-in cluster support for caching data.
Several object-caching frameworks (both open source and commercial implementations) provide distributed caching in servlet containers and application servers. A list of some of the currently available frameworks follows:
- Java Caching System (JCS)
- Java Object Cache (JOCache)
- Java Caching Service, an open source implementation of the JCache API (SourceForge.net)
- IronEye Cache
- SpiritCache (from SpiritSoft)
- Coherence (Tangosol)
- ObjectCache (ObjectStore)
- Object Caching Service for Java (Oracle)
If you are interested in reading more about these caching implementations, see Resources for links to all these frameworks.
Factors to consider in an object-caching framework
Look for the following factors in a caching framework: