The follow up discussion to a recent blog post by Ray Camden made me realize that there's a lot of misunderstanding (including the ColdFusion documentation) about how ColdFusion's user defined caches work. In this blog post, I hope to clear up some of that confusion.
ColdFusion 9.0 completely overhauled CF's caching mechanism and replaced the old file based system with Ehcache. Out of the box, ColdFusion made two default caches available to every ColdFusion application, an object cache and a template cache. It was also possible to create additional user-defined caches at runtime, but only if you were using the cfcache tag, not the caching functions. Here's an example that puts and gets data from a user defined cache:
ColdFISH is developed by Jason Delmore. Source code and license information available at coldfish.riaforge.org
<!--- attempt to get the artist query from the custom object cache --->
<cfcache
key="myCustomObjectCache"
action="get"
id="artistQuery"
name="getArtists"
metadata="myMeta">
<!--- if the item isn't there, it'll return null. In that case, run the query
and cache the results and rerun the data from the db instead --->
<cfif isNull(getArtists)>
<cfquery name="getArtists" datasource="cfartgallery">
SELECT *
from artists
</cfquery>
<cfcache
key="myCustomObjectCache"
action="put"
id="artistQuery"
value="#getArtists#">
</cfif>
<!--- dump the query from cache --->
<cfdump var="#getArtists#">
1<!--- attempt to get the artist query from the custom object cache --->
2<cfcache
3 key="myCustomObjectCache"
4 action="get"
5 id="artistQuery"
6 name="getArtists"
7 metadata="myMeta">
8
9<!--- if the item isn't there, it'll return null. In that case, run the query
10 and cache the results and rerun the data from the db instead --->
11<cfif isNull(getArtists)>
12 <cfquery name="getArtists" datasource="cfartgallery">
13 SELECT *
14 from artists
15 </cfquery>
16
17 <cfcache
18 key="myCustomObjectCache"
19 action="put"
20 id="artistQuery"
21 value="#getArtists#">
22</cfif>
23
24<!--- dump the query from cache --->
25<cfdump var="#getArtists#">
ColdFusion 9.0.1 added the ability to use user defined caches in many of the caching functions as well. Here's our previous example written using cacheGet() and cachePut() instead of the cfcache tag:
ColdFISH is developed by Jason Delmore. Source code and license information available at coldfish.riaforge.org
<!--- attempt to get the artist query from the custom object cache --->
<cfset getArtists = cacheGet('artistQuery', 'myCustomObjectCache')>
<!--- if the item isn't there, it'll return null. In that case, run the query
and cache the results and rerun the data from the db instead --->
<cfif isNull(getArtists)>
<cfquery name="getArtists" datasource="cfartgallery">
SELECT *
from artists
</cfquery>
<cfset cachePut('artistQuery', '#getArtists#', 15, 15, 'myCustomObjectCache')>
</cfif>
<!--- dump the query from cache --->
<cfdump var="#getArtists#">
1<!--- attempt to get the artist query from the custom object cache --->
2<cfset getArtists = cacheGet('artistQuery', 'myCustomObjectCache')>
3
4<!--- if the item isn't there, it'll return null. In that case, run the query
5 and cache the results and rerun the data from the db instead --->
6<cfif isNull(getArtists)>
7 <cfquery name="getArtists" datasource="cfartgallery">
8 SELECT *
9 from artists
10 </cfquery>
11
12 <cfset cachePut('artistQuery', '#getArtists#', 15, 15, 'myCustomObjectCache')>
13</cfif>
14
15<!--- dump the query from cache --->
16<cfdump var="#getArtists#">
One thing to note here is that neither cacheGetProperties() nor cacheSetProperties() can be used to configure the properties for a custom cache in ColdFusion 9.0 or 9.0.1. If you want the cache configuration to differ from ColdFusion's default cache configuration, you'll need to configure the cache in your ehcache.xml file:
ColdFISH is developed by Jason Delmore. Source code and license information available at coldfish.riaforge.org
<cache
name="myCustomObjectCache"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="86400"
timeToLiveSeconds="86400"
overflowToDisk="false"
diskSpoolBufferSizeMB="30"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="3600"
memoryStoreEvictionPolicy="LRU"
clearOnFlush="true"
/>
1<cache
2 name="myCustomObjectCache"
3 maxElementsInMemory="10000"
4 eternal="false"
5 timeToIdleSeconds="86400"
6 timeToLiveSeconds="86400"
7 overflowToDisk="false"
8 diskSpoolBufferSizeMB="30"
9 maxElementsOnDisk="10000000"
10 diskPersistent="false"
11 diskExpiryThreadIntervalSeconds="3600"
12 memoryStoreEvictionPolicy="LRU"
13 clearOnFlush="true"
14/>
Obviously it's not always desirable or even possible (say in the case of a shared hosting environment) to hard code configuration settings in the ehcache.xml file. Besides that, anytime you make changes to the XML file you need to restart ColdFusion for them to take effect. While I hope that Adobe adds the ability to set properties for custom caches in a future release of ColdFusion, there is a solution you can take advantage of today in the form of a small UDF I wrote called cacheCreate().
The cacheCreate() UDF allows you to create a new user defined cache at runtime and pass in configuration options that ColdFusion currently requires you to set in your ehcache.xml file. It uses Java to call the underlying Ehcache API and do the heavy lifting for you. Here's the code (you can also get cacheCreate() at cflib.org:
ColdFISH is developed by Jason Delmore. Source code and license information available at coldfish.riaforge.org
<cffunction name="cacheCreate" output="false" returntype="void"
hint="I create a new user defined cache region in Ehcache"
description="I create a new user defined cache region in Ehcache. This function
allows you to also configure the attributes for the custom cache,
something you would normally have to hard code in the ehcache.xml
file if you rely on ColdFusion's built in caching functions. I named
the function cacheCreate() and not cacheNew() in the hopes that a
future version of ColdFusion includes a cacheNew() function with
similar functionality.">
<!--- this is what's configurable as of Ehcache 2.0 (CF 9.0.1). Only required
argument is Name --->
<cfargument name="name" type="string" required="true">
<cfargument name="maxElementsInMemory" type="numeric" default="10000">
<cfargument name="maxElementsOnDisk" type="numeric" default="10000000">
<cfargument name="memoryStoreEvictionPolicy" type="string" default="LRU">
<cfargument name="clearOnFlush" type="boolean" default="true">
<cfargument name="eternal" type="boolean" default="false">
<cfargument name="timeToIdleSeconds" type="numeric" default="86400">
<cfargument name="timeToLiveSeconds" type="numeric" default="86400">
<cfargument name="overflowToDisk" type="boolean" default="false">
<cfargument name="diskPersistent" type="boolean" default="false">
<cfargument name="diskSpoolBufferSizeMB" type="numeric" default="30">
<cfargument name="diskAccessStripes" type="numeric" default="1">
<cfargument name="diskExpiryThreadIntervalSeconds" type="numeric" default="120">
<!--- We need to do this in java because ColdFusion's cacheGetSession() returns
the underlying object for an EXISTING cache, not the generic cache manager --->
<cfset local.cacheManager = createObject('java', 'net.sf.ehcache.CacheManager').getInstance()>
<!--- constructor takes cache name and max elements in memory --->
<cfset local.cacheConfig = createObject("java", "net.sf.ehcache.config.CacheConfiguration").init("#arguments.name#", #arguments.maxElementsInMemory#)>
<cfset local.cacheConfig.maxElementsOnDisk(#arguments.maxElementsOnDisk#)>
<cfset local.cacheConfig.memoryStoreEvictionPolicy("#arguments.memoryStoreEvictionPolicy#")>
<cfset local.cacheConfig.clearOnFlush(#arguments.clearOnFlush#)>
<cfset local.cacheConfig.eternal(#arguments.eternal#)>
<cfset local.cacheConfig.timeToIdleSeconds(#arguments.timeToIdleSeconds#)>
<cfset local.cacheConfig.timeToLiveSeconds(#arguments.timeToLiveSeconds#)>
<cfset local.cacheConfig.overflowToDisk(#arguments.overflowToDisk#)>
<cfset local.cacheConfig.diskPersistent(#arguments.diskPersistent#)>
<cfset local.cacheConfig.diskSpoolBufferSizeMB(#arguments.diskSpoolBufferSizeMB#)>
<cfset local.cacheConfig.diskAccessStripes(#arguments.diskAccessStripes#)>
<cfset local.cacheConfig.diskExpiryThreadIntervalSeconds(#arguments.diskExpiryThreadIntervalSeconds#)>
<cfset local.cache = createObject("java", "net.sf.ehcache.Cache").init(local.cacheConfig)>
<cfset local.cacheManager.addCache(local.cache)>
</cffunction>
1<cffunction name="cacheCreate" output="false" returntype="void"
2 hint="I create a new user defined cache region in Ehcache"
3 description="I create a new user defined cache region in Ehcache. This function
4 allows you to also configure the attributes for the custom cache,
5 something you would normally have to hard code in the ehcache.xml
6 file if you rely on ColdFusion's built in caching functions. I named
7 the function cacheCreate() and not cacheNew() in the hopes that a
8 future version of ColdFusion includes a cacheNew() function with
9 similar functionality.">
10
11 <!--- this is what's configurable as of Ehcache 2.0 (CF 9.0.1). Only required
12 argument is Name --->
13 <cfargument name="name" type="string" required="true">
14 <cfargument name="maxElementsInMemory" type="numeric" default="10000">
15 <cfargument name="maxElementsOnDisk" type="numeric" default="10000000">
16 <cfargument name="memoryStoreEvictionPolicy" type="string" default="LRU">
17 <cfargument name="clearOnFlush" type="boolean" default="true">
18 <cfargument name="eternal" type="boolean" default="false">
19 <cfargument name="timeToIdleSeconds" type="numeric" default="86400">
20 <cfargument name="timeToLiveSeconds" type="numeric" default="86400">
21 <cfargument name="overflowToDisk" type="boolean" default="false">
22 <cfargument name="diskPersistent" type="boolean" default="false">
23 <cfargument name="diskSpoolBufferSizeMB" type="numeric" default="30">
24 <cfargument name="diskAccessStripes" type="numeric" default="1">
25 <cfargument name="diskExpiryThreadIntervalSeconds" type="numeric" default="120">
26
27 <!--- We need to do this in java because ColdFusion's cacheGetSession() returns
28 the underlying object for an EXISTING cache, not the generic cache manager --->
29 <cfset local.cacheManager = createObject('java', 'net.sf.ehcache.CacheManager').getInstance()>
30
31 <!--- constructor takes cache name and max elements in memory --->
32 <cfset local.cacheConfig = createObject("java", "net.sf.ehcache.config.CacheConfiguration").init("#arguments.name#", #arguments.maxElementsInMemory#)>
33 <cfset local.cacheConfig.maxElementsOnDisk(#arguments.maxElementsOnDisk#)>34 <cfset
local.cacheConfig.memoryStoreEvictionPolicy("#arguments.memoryStoreEvictionPolicy#
")>
35 <cfset
local.cacheConfig.clearOnFlush(#arguments.clearOnFlush#)>
36 <cfset
local.cacheConfig.eternal(#arguments.eternal#)>
37 <cfset
local.cacheConfig.timeToIdleSeconds(#arguments.timeToIdleSeconds#)>
38 <cfset
local.cacheConfig.timeToLiveSeconds(#arguments.timeToLiveSeconds#)>
39 <cfset
local.cacheConfig.overflowToDisk(#arguments.overflowToDisk#)>
40 <cfset
local.cacheConfig.diskPersistent(#arguments.diskPersistent#)>
41 <cfset
local.cacheConfig.diskSpoolBufferSizeMB(#arguments.diskSpoolBufferSizeMB#)>
42 <cfset
local.cacheConfig.diskAccessStripes(#arguments.diskAccessStripes#)>
43 <cfset
local.cacheConfig.diskExpiryThreadIntervalSeconds(#arguments.diskExpiryThreadIntervalSeconds#)>
4445 <cfset
local.cache = createObject("java
", "net.sf.ehcache.Cache
").init(local.cacheConfig)>
46 <cfset
local.cacheManager.addCache(local.cache)>
47</cffunction>
To create and configure a new user defined cache in your application, all you need to do is something like this:
ColdFISH is developed by Jason Delmore. Source code and license information available at coldfish.riaforge.org
<!--- let's build a struct of arguments. It's much easier to pass this way --->
<cfset myProps = structNew()>
<cfset myProps.name = "myCustomCache">
<cfset myProps.maxElementsInMemory = 10>
<cfset myProps.maxElementsOnDisk = 10>
<cfset myProps.memoryStoreEvictionPolicy = "FIFO">
<cfset myProps.clearOnFlush = true>
<cfset myProps.eternal = false>
<cfset myProps.timeToIdleSeconds = 86400>
<cfset myProps.timeToLiveSeconds = 86400>
<cfset myProps.overflowToDisk = false>
<cfset myProps.diskPersistent = true>
<cfset myProps.diskSpoolBufferSizeMB = 30>
<cfset myProps.diskAccessStripes = 1>
<cfset myProps.diskExpiryThreadIntervalSeconds = 120>
<!--- create the new custom cache region --->
<cfset cacheCreate(argumentCollection=myProps)>
<!--- prove that it's there the CF way --->
<cfdump var="#cacheGetSession('myCustomCache', 'true').getCacheManager().getCacheNames()#">
1<!--- let's build a struct of arguments. It's much easier to pass this way --->
2<cfset myProps = structNew()>
3<cfset myProps.name = "myCustomCache">
4<cfset myProps.maxElementsInMemory = 10>
5<cfset myProps.maxElementsOnDisk = 10>
6<cfset myProps.memoryStoreEvictionPolicy = "FIFO">
7<cfset myProps.clearOnFlush = true>
8<cfset myProps.eternal = false>
9<cfset myProps.timeToIdleSeconds = 86400>
10<cfset myProps.timeToLiveSeconds = 86400>
11<cfset myProps.overflowToDisk = false>
12<cfset myProps.diskPersistent = true>
13<cfset myProps.diskSpoolBufferSizeMB = 30>
14<cfset myProps.diskAccessStripes = 1>
15<cfset myProps.diskExpiryThreadIntervalSeconds = 120>
16
17<!--- create the new custom cache region --->
18<cfset cacheCreate(argumentCollection=myProps)>
19
20<!--- prove that it's there the CF way --->
21<cfdump var="#cacheGetSession('myCustomCache', 'true').getCacheManager().getCacheNames()#">
While this won't work in all cases (say you want to cluster your cache or add in a Terracotta Server Array), it should get you thinking about other ways you can extend ColdFusion's ehcache implementation by utilizing some of the Ehcache Java API within CF.
6/23/11 5:04 AM
Does the code you've written update ehcache.xml? Or does it need to be run again after a ColdFusion restart?
6/23/11 5:43 PM
Matthew, caches created at runtime don't have their config written to the ehcache.xml file. This doesn't mean that they have to be recreated every time on application start, though. Depending on how you've configured them they can be set to persistent so they survive JVM restarts. I'll blog a little more on this next.
3/6/12 8:56 AM
is there a way to do "if customcache doesnt exist create it" in coldfusion 9?
6/13/12 12:33 PM
Hey Rob - I added a minor bit to the end to make sure the cache doesn't exist:
<cfif !local.cacheManager.cacheExists(arguments.name)>
<cfset local.cacheManager.addCache(local.cache)>
</cfif>
Since I'm running this in onApplicationStart I wanted to avoid errors when I reinit the app if I try to re-create a cache that already exists. See any issue with this?
7/18/12 12:07 AM
Hey Rob, really nice entry and idea here. Thanks for it.
A couple of observations: don't you mean to have a definition of maxelementsinmemory within the function? You show passing it in, but are not using it.
Also, what about diskstore? I tried to add it but could not. I know you said in other entries that CF did not permit mod'ing that programmatically, but do you think this class has the same issue?