Caching

Caching is extensively used for optimizing database applications and effectively reducing traffic between the database and the application.

ColdFusion ORM supports two levels of caching:

  • Session level

  • Secondary level

Session level cache

Objects that are loaded from the database are always cached in the ORM Session as long as the session is open. When EntityLoad is called to retrieve an object in a session for the first time, ORM fetches the data from the database and constructs the object. In any subsequent call to load the same object in the same session, ORM fetches the object from the session cache. To forcefully retrieve the object from the database, EntityReload should be called on the object.

For more details on ORM Sessions and its lifecycle, see ORM session management and Architecture.

Secondary level cache

ColdFusion provides the ability to store the data that is retrieved from the database in secondary cache. The contents in secondary cache live longer than the life-time of a session. It can also be the life-time of the process or in-definite (disk-caching), depending on the ability of the secondary cache provider. The cache can also be used in a distributed environment depending on the ability of the secondary cache provider.

An important difference between session level cache and secondary level cache is that the session level caches the whole object but the secondary level caches only the data.

Secondary level cache can be leveraged by using an external cache provider with ColdFusion ORM. EHCache, JBossCache, OSCache, SwarmCache, and Tangosol Coherence Cache are some popular secondary cache providers, which can be plugged into Hibernate.

ColdFusion uses EHCache as the default secondary cache provider. EHCache is a distributed caching solution that supports memory and disk-based caching. EHCache can be configured using a configuration file. Different cache regions can be defined in the configuration file. Each cache region has its own configuration that specifies details including the number of elements it can store, eviction policy, time to live (ttl), and idle time.

ehcache.xml is available in the following location: CF_root\lib\. For details of the properties in the ehcache.xml, refer to the documentation available at the following URL:

http://ehcache.org/

The following is a sample EHCache configuration file (ehcache.xml):

<ehcache> 
    <diskStore path="java.io.tmpdir"/> 
    <defaultCache 
        maxElementsInMemory="10000" 
        eternal="false" 
        timeToIdleSeconds="120" 
        timeToLiveSeconds="120" 
        overflowToDisk="true" 
        diskPersistent="false" 
        diskExpiryThreadIntervalSeconds="120" 
        memoryStoreEvictionPolicy="LRU" 
        />        
    <cache name="Artist" 
        maxElementsInMemory="20" 
        eternal="true" 
        overflowToDisk="false" 
        /> 
</ehcache>

Modifications to ehcache.xml in ColdFusion 9.0.1

ehCache.xml includes the following configuration properties:

  • diskSpoolBufferSizeMB: Size to allocate the DiskStore for a spool buffer.

    The default size is 30 MB. Each spool buffer is used only by its cache.

    Turning on trace-level logging shows if backup for cache created/updated using action="put" occurs in the diskstore.

  • clearOnFlush: Determines if the MemoryStore must be cleared when the cache is flushed. By default, the MemoryStore is cleared.

  • diskExpiryThreadIntervalSeconds: The number of seconds between runs of the disk expiry thread. The default value is 120 seconds.

Note: The functions cacheGetProperties and cacheSetProperties can be used to get/set these properties.

Use secondary cache

To use secondary cache, you must configure the following settings in the application:

  • ormsettings.secondarycacheenabled

    This setting defines whether the secondary cache would be used by the application. By default, this is set to false.

  • ormsettings.Cacheprovider

    This setting defines the cache provider that needs to be used for secondary cache. This defaults to EHCache. The other values for this setting are JBossCache, OSCache, SwarmCache and Hashtable. You can also specify the fully qualified class name of the cache provider.

  • ormsettings.cacheconfig

    This setting defines the configuration file required by the secondary cache provider. For example, EHCache requires EHCache.xml that defines the configuration settings for the secondary cache. Specify the path to the XML file in this setting. If this setting is not defined, cache provider uses its default configuration.

After you have configured the secondary cache, it is critical to identify the objects in your application that can be cached because the data cached by secondary cache is shared by all the sessions of an application. Typically, caching should be enabled for a CFC that represents:

  • Data that changes rarely

  • Data that is local to an application and is not modified by other applications

  • Non-critical data

For each type of object that needs to be cached, you also need to decide the access strategies. ORM provides the following cache strategies that you can use for your objects:

  • read-only

    This strategy is useful for data that is read frequently but never updated. This is the best performing cache strategy.

  • nonstrict-read-write

    This strategy is useful for data that is updated occasionally. Typically, it is very unlikely that two transactions would update the same object simultaneously.

  • read-write

    This strategy may be appropriate if your data needs to be updated. It carries more overhead than the two preceding strategies.

  • Transactional

    This strategy provides the support for transactional cache. It can only be used if the cache provider is transaction aware.

Support for these strategies depend on the cache provider. Not all the cache providers support all the cache strategies. For more information on these strategies, see:

www.hibernate.org/hib_docs/reference/en/html/performance-cache.html

The secondary cache can cache the following types of data.

  • Persistent object data

  • Persistent object association

  • Query data

Cache data of a persistent object

In this case, the data of the persistent object is cached. It will not cache the associations or associated object's data. To enable this flag on a persistent CFC, specify the following attributes on the component.

  • cacheuse: Defines the caching strategy.

  • cachename: Defines the name of the cache region to be used by the secondary cache provider. If you do not specify a region name for the component, the entity name of the component is considered as the cache name. In case a region is not specified in the configuration file, a region is automatically created with the default configuration.

For example:

<cfcomponent persistent="true" schema="APP" table="Artists" cachename="artist" cacheuse="read-only">

Cache the association data of a persistent object

In this case, the primary key of the associated objects are cached. It does not cache the objects loaded as part of the association unless caching is enabled for those objects. To cache an association, specify the following attributes on the association property.

  • cacheuse: Defines the caching strategy.

  • cachename: Defines the name of the cache region to be used by the secondary cache provider. If you do not specify a region name for the association property, the <comoponent_name>.<property_name> is considered as the cache name. In case a region is not specified in the configuration file, a region is automatically created with the default configuration.

For example:

<cfproperty name="art" fieldtype="one-to-many" cfc="CArt" fkcolumn="ArtID" cachename="ArtistArts" cacheuse="read-only">

Cache query data

In this case, the results of queries that are executed by ORMExecuteQuery() or EntityLoad() methods are cached in the secondary cache. To enable caching query data, pass "cacheable=true" and "cachename='cachename' values in the options struct of the methods. If you do not specify the cachename, the query is cached in the default query cache. It is recommended that you to specify the cachename so that you can control eviction.

For example:

availableArts = ORMExecuteQuery("from CArt where issold=0", {}, false, {cacheable=true, cachename="availableArtsQuery"});

Secondary cache example using EHCache

Step 1: Set the following in Application.cfc:

<cfset this.name="Caching_Example"> 
<cfset this.datasource="cfartgallery"> 
<cfset this.ormenabled="true"> 
<cfset this.ormsettings.secondarycacheEnabled=true> 
<cfset this.ormsettings.cacheProvider= "ehcache"> 
<cfset this.ormsettings.cacheConfig="ehcache.xml">

Step 2: Define the cache settings in the CFCs.

CArtist.cfc

<cfcomponent persistent="true" schema="APP" table="Artists" cachename="artist" cacheuse="read-only"> 
    <cfproperty name="artistid" fieldtype="id"/> 
    <cfproperty name="firstname"/> 
    <cfproperty name="lastname"/>   
    <cfproperty name="state"/> 
    <cfproperty name="art" fieldtype="one-to-many" cfc="CArt" fkcolumn="ArtID" cachename="ArtistArts" cacheuse="read-only"> 
</cfcomponent>

CArt.cfc

<cfcomponent persistent="true" schema="APP" table="Art"> 
    <cfproperty name="artid" generator="identity" fieldtype="id"/> 
    <cfproperty name="artname"/> 
    <cfproperty name="issold"/> 
</cfcomponent>

Step 3:

<cfscript> 
//This will cache the Artist Component and also the association.  It wouldn't cache the Art objects. 
artistObj = EntityLoad("CArtists", 3, true); 
//This will cache the query. 
availableArts = ORMExecuteQuery("from CArt where issold=0", {}, false, {cacheable=true, cachename="availableArtsCache"}); 
</cfscript>

Evict content from secondary cache

ColdFusion provides the following methods to evict contents from the secondary cache.

ORMEvictEntity("<component_name>", [primarykey])

This method is used to evict items for the given component name, from the secondary cache. If the primary key is specified, then the data of the entity with that primary key is evicted. Primary key should be a value in case of simple primary key or should be a struct in case of composite primary key.

Example:

<cfset ORMEvictEntity("CArtists")> 

Evicts all the cache data of CArtist entity.

<cfset ORMEvictEntity("CArtists", 1)> 

Evicts the cache data of CArtists entity whose primary key is 1.

ORMEvictCollection("<component_name>", "<collection_name>", [primarykey])

This method is used to evict all the collection/association data for the given component name and collection name, from the secondary cache. If the primary key is specified, then, the collection or association data of the entity with the primary key is evicted.

Example:

<cfset ORMEvictCollection("CArtists", "art")> 

Evicts all the association or collection data of collection art belonging to the component CArtists.

<cfset ORMEvictCollection("CArtists", "art", 1)> 

Evict the association or collection data of collection art belonging to the component CArtists with primary key 1.

ORMEvictQueries([cachename])

This method is used to evict the data of all the queries from the default query cache. If cache name is specified, then, the data of all the queries belonging to the cache region with the given cache name are evicted. Example:

<cfset ORMEvictQueries()> 

Evicts the data of all the queries from the default query cache.

<cfset ORMEvictQueries("availableArtsCache")> 

Evicts the data of all the queries from the cache region with the name availableArtsCache.

Support for user-defined caches in ColdFusion 9.0.1

Except in cacheSetProperties and cacheGetProperties, user-defined caches are supported in all caching functions.

Edit ehCache.xml (cfroot/lib)to set the properties for user-defined caches as shown in the following example:

<!--- item to put in user-defined cache ---> 
<cfset currentTime = Now()> 
<!--- put item in user-defined cache ---> 
<cfset timeToLive=createtimespan(0,0,0,30)> 
<cfset timeToIdle=createtimespan(0,0,0,30)> 
<cfset customCache = "usercache"> 
<cfset id = "cache1"> 
<cfset cachePut(id,currentTime,timeToLive,timeToIdle,customCache)> 
<!-- list items in the cache ---> 
List Items in cache:  
<cfset cacheIds = cacheGetAllIds(customCache)> 
<cfdump var="#cacheIds#"><br> 
<!--- print cache data ---> 
<cfset cachedData = cacheGet(id,customCache)> 
<cfoutput>#cachedData#</cfoutput> 
<!--- print cache metadata ---> 
Cache metadata: 
<cfset mdata = cacheGetMetadata(id,"object",customCache)> 
<cfdump var="#mdata#"> 
<!--- clear user-defined cache ---> 
<cfset cacheRemove(ArrayToList(cacheIds),true,customCache)>