Introduction to Caching in Mach-II 1.6

Overview

Mach-II 1.6 includes a built-in way to cache data retrieved by listeners and output generated by views. In other words, you can cache the result of one or more listener calls (i.e. notify commands specified in the XML configuration file) and the result of a rendered view (i.e. view-page commands specified in the XML configuration file). Calls to subroutines can also be cached.

The developer can control the state of cache and clear it when data in the cache has become stale. For example, if a user updates a list of parts and this list is cached by Mach-II, the developer can clear the parts cache on demand, which causes the parts cache to be re-populated the next time the parts list is requested.

Two caching strategies are included with the Mach-II framework: Time Span and Least Recently Used (LRU). Developers have the ability to write their own and have it extend the AbstractCacheStrategy component.

This tutorial will show you how to use the Caching Property and how to alter the defaults to your liking. This tutorial assumes you already have a basic understanding of Mach-II (editing the config.xml, calling an event, etc.).

This feature was developed under the Mach-II specification and feedback process (M2SFP). For information on the evolution of this feature, please read the  original caching specification.

Getting Started

Adding the Caching Property requires you to put the following line of xml within the <properties> section of your config.xml file:

<property name="Caching" type="MachII.caching.CachingProperty"/>

With no additional parameters specified, this will set the default settings for caching (which is one hour in the application scope - for more information see below). To add something to the cache, you will also need to add the cache command to an event-handler. Below is an example of caching the view of a web site's home page.

This example is the barest form using only one command in the cache handler, but you can nest as many commands in the cache as you want:

<event-handler name="home" access="public">
	<cache>
		<view-page name="home" />
	</cache>
</event-handler>

The cache command instructs Mach-II to save the "home" view into an application scoped variable and use it instead of reading home.cfm each time this event is called.

What Is Cached

The <cache> command only caches data that is located in the event that:

  • has been added to the event by a command or other code inside the <cache> block
  • was changed by a command or other code inside the <cache> block

The <cache> command does not cache data that:

  • was set to the event or existed in the event before the <cache> block was executed
  • does not exist in the event object such as in an external scope like the request scope (this affects users of the deprecated `resultKey` and `contentKey` features)

Caching Related Commands

Mach-II ships with commands available for your use in the Mach-II XML configuration file. Below is information on the command attributes with examples on how to use them.

cache

Attribute Values Required Default Description
id unique id optional Unique id this cache block is to be known by
aliases list of aliases optional List of aliases this cache can be referenced by later on such as clearing the cache.
strategyName name of cache strategy to use optional The name of the cache strategy you wish to use as specified in the parameter for the Caching property. If left out, defaultCacheName will be used.
criteria optional true Comma delimited list of optional criteria to further identify the storage of your data. See below for more information.

Cache criteria: The criteria allows you to define additional identification to your cache such as an url parameter or other event argument. For example, perhaps you want to cache the output for several items in your product catalog and they are identified by itemId. Any variable listed here will be pulled from the event arguments. If the event-argument does not exist, it will default to blank. The following uses itemId as its caching criteria:

<cache aliases="itemCache" criteria="itemId">

OR

<cache aliases="itemCache" criteria="${event.itemId}">

Separate caches would be created by each of the the following URLs.

/index.cfm?event=listenerTest&ItemId=99

/index.cfm?event=listenerTest&ItemId=1

Both of these requests would be cached independently from each other using the 'itemId' as one component of the caching criteria. In terms of its concrete implementation, the itemId is used to compose the identity key.

Additionally, you can enter something like "itemId=itemId" and Mach-II will translate that as "itemID=99". You can specify multiple criteria as a comma-delimted list (e.g., criteria="mainCategory=mainCatId,subCategory=subCatId").

Remember that you cannot use <announce>, announceEvent(), announceEventInModule() or <redirect> inside of a cache command as these actions are not compatible with the caching infrastructure (i.e. cached data does not replay event announcements or redirects).

cache-clear

Attribute Values Required Default Description
ids unique ids optional List of unique ids to clear
aliases list of aliases optional List of aliases to clear
strategyNames list of cache strategy names optional The names of the cache strategies you wish to clear as specified in the parameter for the Caching property. If left out, defaultCacheName will be used.
criteria optional true Comma delimited list of optional criteria to further identify which items in the cache you want to clear. See below for more information.
condition expression optional A expression that must be met in order for the cache to be cleared.

N.B. A few attribute names differ from the attributes available in the cache command. Those attributes are aliases, ids and strategyNames which are all plural however in the cache command they are singular. This is because cache-clear support clearing multiple aliases, id or cache strategies by name at once. Please beware of these differences as leaving of aliases or ids when trying to clear a specific element will essentially clear the entire cache because no aliases or ids were specified.

The cache-clear command also accepts the strategyNames attribute, so an entire caching strategy may be cleared programmatically in one command. You can also specify the ids, aliases and criteria attributes so that you clear out a specific item in the cache. Here is an example event-handler with cache-clear:

Clears cache blocks by alias acctNav where the criteria is user_id key is value returned from an event arg named userId:

<event-handler event="logout">
	<cache-clear alias="acctNav" criteria="user_Id=${event.userId}" />
	<notify listener="AccountListener" method="logoutUser" />
	<announce event="home" copyEventArgs="true" />
</event-handler>

Clears an entire cache strategy by name of general:

<event-handler event="logout">
	<cache-clear strategyNames="general" />
	<notify listener="AccountListener" method="logoutUser" />
	<announce event="home" copyEventArgs="true" />
</event-handler>

Clears a cache block with id of home when the condition evaluates to true:

<event-handler event="logout">
	<cache-clear ids="home" condition="${event.someArg} EQ 1" />
	<notify listener="AccountListener" method="logoutUser" />
	<announce event="home" copyEventArgs="true" />
</event-handler>

Clears the entire default cache strategy:

<event-handler event="logout">
	<cache-clear />
	<notify listener="AccountListener" method="logoutUser" />
	<announce event="home" copyEventArgs="true" />
</event-handler>

Caching Strategies

The Time Span and Least Recently Used (LRU) Caching strategies are included with Mach-II. The CFCs can be found in \MachII\caching\strategies. Additional caching strategies will be added in future versions of Mach-II. You can also write your own strategy that could interface with distributed caching systems like  MemCached or  Ehcache by writing a concrete CFC that extends MachII.caching.strategies.AbstractCacheStrategy.

Time Span Cache

The default cache strategy is the Time Span strategy. This allows the developer to define an amount of time that the data in question should be cached. The Notes section of the comments in the MachII.caching.strategies.TimeSpanCache shows all the configuration options available:

<property name="Caching" type="MachII.caching.CachingProperty">
	<parameters>
		<!-- Naming a default cache name is not required, but required if you do not want 
			to specify the 'name' attribute in the cache command -->
		<parameter name="defaultCacheName" value="default" />
		<parameter name="default">
			<struct>
				<key name="type" value="MachII.caching.strategies.TimeSpanCache" />
				<key name="cachingEnabled">
					<struct>
	    		        		<!-- All key names prefixed with 'group:' indicate environment groups -->
	    		        		<key name="group:development,qa" value="false"/>
	    		        		<!-- Explicitly setting environment names of 'prod' and 'prodFailOver' -->
			            		<key name="prod,prodFailOver" value="true"/>
					</struct>
				</key>
				<key name="scope" value="application" />
				<key name="timespan" value="0,1,0,0" />
				<key name="cleanupIntervalInMinutes" value="3" />
			</struct>
		</parameter>
	</parameters>
</property>

As noted above, the defaultCacheName is not required, but a good idea to name the default cache so that you won't have to specify the cache name within the event handler. Setting a default is useful if you wish to use more than one caching strategy. For example, you may want some items cached for 1 hour and other items cached for 24 hours.

You then define each cache giving it a name and passing in a structure of various keys which are explained below. Each key tag above is explained below.

Key Description Required Values Default Value
type The cache strategy to use which in this case is MachII.caching.strategies.TimeSpanCache. yes CFC dot path n/a
cachingEnabled Turns caching on or off no boolean or struct of environment names / groups (prefixed with group:) with corresponding booleans (struct supported in Mach-II 1.8+) true
scope The scope that the cache should be placed in. no application, server, session application
timespan Takes a string formatted like CFML's CreateTimeSpan() function that indicates the cache for time span. The list of 0,0,0,0 is days, hours, minutes, seconds or may take the value of forever. no numeric list values or forever 0,1,0,0 (1 hour)
cleanupIntervalInMinutes The number of minutes in which to run the reap() method. Reap removes expired elements from the cache, but does not "refresh" the data. Data is added back into the cache only when an event-handler requests that data. no numeric value 3

Using all of the default settings will result in caching each element of data for 1 hour in the application scope. Expired cache elements will be cleaned up via reap() which is run every 3 minutes.

LRU Cache

LRU stands for Least Recently Used and is a caching policy that keeps the most recently accessed data in the cache and discards least accessed data first. You can visualize the LRU cache as a sized array. Elements at the top of the array are the most recently accessed data while elements at the bottom are older. At a certain point, old data exceeds the size of the array and is discarded. LRU caches are somewhat more process intensive as each time a new piece of data is added, all other pieces of data must shift so the least recently used piece of data is discarded first. LRU caches are better suited for smaller datasets that change infrequently.

The Notes section of the comments in the MachII.caching.strategies.LRUCache shows all the configuration options available:

<property name="Caching" type="MachII.caching.CachingProperty">
      <parameters>
            <!-- Naming a default cache name is not required, but required if you do not want 
                 to specify the 'name' attribute in the cache command -->
            <parameter name="defaultCacheName" value="default" />
            <parameter name="default">
                  <struct>
                        <key name="type" value="MachII.caching.strategies.LRUCache" />
			<key name="cachingEnabled">
				<struct>
	    		        	<!-- All key names prefixed with 'group:' indicate environment groups -->
	    		        	<key name="group:development,qa" value="false"/>
	    		        	<!-- Explicitly setting environment names of 'prod' and 'prodFailOver' -->
		            		<key name="prod,prodFailOver" value="true"/>
				</struct>
			</key>
                        <key name="size" value="100" />
                        <key name="scope" value="application" />
                  </struct>
            </parameter>
      </parameters>
</property>

You then define each cache giving it a name and passing in a structure of various keys which are explained below. Each key tag above is explained below.

Key Description Required Values Default Value
type The cache strategy to use which in this case is MachII.caching.strategies.LRUCache. yes CFC dot path n/a
cachingEnabled Turns caching on or off no boolean or struct of environment names / groups (prefixed with group:) with corresponding booleans (struct supported in Mach-II 1.8+) true
size The size of the LRU cache size before data is discarded. no numeric 100
scope The scope that the cache should be placed in. no application, server, session application

Using all of the default settings will result in caching 100 elements of data in the application scope with least recently used elements of data being discarded first.

Multiple Caching Strategies

It is possible to use more than one caching strategy. One example is to use the Time Span Cache strategy twice for different lengths of time.

<property name="Caching" type="MachII.caching.CachingProperty">
	<parameters>
		<!-- Naming a default cache name is not required, but required if you do not want 
			 to specify the 'name' attribute in the cache command -->
		<parameter name="defaultCacheName" value="static_content" />
		<parameter name="static_content">
			<struct>
				<key name="type" value="MachII.caching.strategies.TimeSpanCache" />
				<key name="scope" value="application" />
				<key name="cacheFor" value="12" />
				<key name="cacheForUnit" value="hours" />
				<key name="cleanupIntervalInMinutes" value="10" />
			</struct>
		</parameter>
		<parameter name="user_specific">
			<struct>
				<key name="type" value="MachII.caching.strategies.TimeSpanCache" />
				<key name="scope" value="application" />
				<key name="cacheFor" value="60" />
				<key name="cacheForUnit" value="minutes" />
				<key name="cleanupIntervalInMinutes" value="3" />
			</struct>
		</parameter>
	</parameters>
</property>

And here are two event-handlers showing how to reference these. Note that the first one does not specify a cacheName, therefore it will use the default specified above - "static_Content". The second event-handler will cache the user's account page and create a separate cache based on the userId

<event-handler event="home">
	<cache aliases="homePage">
		<view-page name="home" />
	</cache>
</event-handler>

<event-handler event="myAccountHome">
	<cache aliases="myAccountHomePage" strategyName="user_specific" criteria="userId">
		<notify listener="AccountListener" method="getUserInfo" resultArg="qryUserInfo">
		<view-page name="myAccountHomeView" />
	</cache>
</event-handler>

Advanced Information

Using the Caching Stats

All cache strategies have a caching stats CFC that provides information regarding the performance of the cache. The stats include total hits, misses, evictions, active elements and total elements. Also, one calculation is performed that indicates the hit ratio of the cache. The hit ratio is a percentage that indicates the general performance of the strategy as it is current configured and is computed the total hits divided by total accesses (hits + misses). In order to get accurate data, your cache strategy must be active for a period of time (minutes to hours depending on load). Most developers should aim for a hit ratio above 75% where ratios in the 90%+ are ideal.

You can manually get the caching stats CFC by calling:

getProperty("nameOfCachingProperty").getCacheStrategyManager().getCacheStrategyByName("nameOfDesiredStrategy").getCacheStats()

Or even easier, use the Mach-II Dashboard module which has charts of the cache efficiency in addition to the raw data and other extremely useful functionality wrapped up in a nice GUI.

General Usage Notes

Caching data can yield unexpected results if not used correctly. Here are some general notes to keep in mind.

  • An error will be thrown if you use the <announce>, <event-mapping> or <redirect> commands within cache tags as they will not cache correctly.
  • Likewise, you should not cache a listener notification that announces a new event. The new event will not be announced if that command or listener method is cached as event announcements cannot be replayed when using cached data.
  • Also, filter commands that possibly redirect events should not be cached as redirects will not be replayed when using cached data.
  • Use the criteria attribute to differentiate content that varies based on form or url parameters.
  • When using clear-cache with the criteria attribute, you may have to manually put those criteria into the event arguments before the clear-cache command. This would be needed if the event with clear-cache does not normally have those arguments.
  • Mach-II will not cache data when using deprecated features like contentKey and resultKey.
  • Mach-II has to recreate the cache when Mach-II is reloaded if you are using the time span or LRU caches.
  • Cache will not replay text inserted in the head via <cfhtmlhead>. Use addHTMLHeadElement("textToInsert") in views or arguments.eventContext.addHTMLHeadElement("textToInsert") in places where there is access to the EventContext to have the cache replay.
  • Cache will not replay headers set via <cfheader>. Use addHTTPHeader(name="some", value="thing", charset="utf-8"), addHTTPHeader(statuscode="500", statustext="Some Text"), addHTTPHeaderByName(name, value) or addHTTPHeaderByStatus(statuscode, statustext) in views or same method names in the EventContext to have the cache replay.

Special thanks to Matt Williams (mgw4jc@…) for contributing the initial version of this wiki entry.