Aliaspooryorik
ColdFusion ORM Book

Caching views but not layouts in ColdBox

ColdBox has some awesome caching capabilities which can really improve your application's performance. I wanted to have a closer look at the options available in ColdBox 3 (using the latest version from github) and thought I'd post some sample code here as well in case it's useful for anyone else.

First let's start off with a typical event handler with no caching at all (BTW: These samples are written in script, but tags work the same).


/**
* @hint I'm the General event handler
**/

component
{
/**
* @hint I am a normal event with no caching
**/

void function index( required event )
{
var rc = arguments.event.getCollection();
// simulate some really slow code needed to render view
sleep( 5000 );

arguments.event.setView( "General/index" );
}

}

As you can see I've also put a call to the sleep function to simulate a slow process required to create the view. Next up I'm going to create a simple view and Layout.


<!--- views/General/index.cfm --->
<cfoutput>
<p>view rendered: #Now()#</p>
</cfoutput>

<!--- layouts/Layout.Main.cfm --->
<cfoutput>
<html>
<head>
<title>Coldbox caching example</title>
</head>
<body>
<p>Layout Rendered: #Now()#</p>
#renderView()#
</body>
</html>
</cfoutput>

When I run index.cfm?event=General in my browser, sure enough 5 seconds later I get to see:


Layout Rendered: {ts '2010-12-07 14:20:41'}
view rendered: {ts '2010-12-07 14:20:41'}

If I run it again, then another 5 seconds later I see.


Layout Rendered: {ts '2010-12-07 14:21:32'}
view rendered: {ts '2010-12-07 14:21:32'}

Clearly, this event is slow so some caching would be a good idea. The simplest way it to cache the whole event. I'm going to add a new 'cachedevent' event to the handler with the relevant metadata so we can compare. The view/General/cachedevent.cfm is a the same as the index.cfm above.


/**
* @hint I am a cached event
* @cache true
* @cacheTimeout 5
* @cacheLastAccessTimeout 2
**/

void function cachedevent( required event )
{
var rc = arguments.event.getCollection();

// simulate some really slow code needed to render view
sleep( 5000 );

arguments.event.setView( "General/cachedevent" );
}

When I run index.cfm?event=General.cachedevent in my browser, sure enough 5 seconds later I get to see:


Layout Rendered: {ts '2010-12-07 14:26:56'}
view rendered: {ts '2010-12-07 14:26:56'}

However, the second time I run it, then, ummm, it still takes 5 seconds. Something is wrong here. I didn't update the config/Coldbox.cfc to allow event caching. Simply update the false flag to true so you get this:


eventCaching = true

You'll also need to reload ColdBox to pick up the changes. Now when you run the event, sure enough, first time we get this:


Layout Rendered: {ts '2010-12-07 14:30:26'}
view rendered: {ts '2010-12-07 14:30:26'}

But the second time you run it it's much faster and you'll see:


Layout Rendered: {ts '2010-12-07 14:30:26'}
view rendered: {ts '2010-12-07 14:30:26'}

As you may have noticed, the timestamp is the same as it was the first time. Now this is all great, but there are a few things to note. Firstly the event is cached based on the parameters passed to it, so if you call index.cfm?event=general.cachedevent&foo=1 and then call index.cfm?event=general.cachedevent&foo=2, it will not be pulled from the cache. The second thing to note is that the whole event is cached including the view and layout. So what happens if we are building something like an ecommerce site which showed the number of items in the basket as part of the layout. With event caching, the number of items in the basket will not update. So how do we cache views but not the layout? Actually, it's really easy, let's add a new 'cachedview' event.


/**
* @hint I only cache the view not the layout
**/

void function cachedview( required event )
{
var rc = arguments.event.getCollection();

// make sure that this event is not cached if arguments change
var cachesuffix = Hash( SerializeJSON( [ URL, FORM ] ) );

// simulate some really slow code needed to render view
sleep( 5000 );

arguments.event.setView( name="General/cachedview", cache="true", cacheTimeout="5", cacheLastAccessTimeout="2", cacheSuffix=cachesuffix );
}

As you can see, I've got rid of the metadata which tells ColdBox to cache the event, instead I've added some arguments to the setView method. These are pretty self expanatory, expect for the cacheSuffix. If I didn't use this parameter, then the view would be cached regardless of what arguments we pass to the event. I've created a Hash from the FORM and URL scopes so that I can make sure that the view is only retrieved from the cache if the URL and/or FORM scopes are the same.

When I run index.cfm?event=General.cachedview, the first time it's slow and I see:


Layout Rendered: {ts '2010-12-07 14:44:33'}
view rendered: {ts '2010-12-07 14:44:33'}

The second time I run it, it's still slow but I get:


Layout Rendered: {ts '2010-12-07 14:44:41'}
view rendered: {ts '2010-12-07 14:44:33'}

So, we've solved the problem of wanting to only cache the view and not the layout, but we're still calling the sleep( 500 ) method on each request. We need to do something about this.

Luckily ColdBox has a nice API for the cache so we can simply check to see if the view is already in the cache. If it is then we don't need to run the slow code, if it isn't cached then we need to run the slow code so that we can render the view (think of a complex report if you want a real world example).

Time for yet another new event which I've called 'templatecache'


/**
* @hint I cache the view not the layout and only run the model if required
**/

void function templatecache( required event )
{
var rc = arguments.event.getCollection();

// get the template cache
var TemplateCache = getColdboxOCM( "template" );
// set the view we want to render
var theview = "General/templatecache";
// make sure that this event is not cached if arguments change
var cachesuffix = Hash( SerializeJSON( [ URL, FORM ] ) );
// construct the key name
var keyname = TemplateCache.getViewCacheKeyPrefix() & ":" & theview & cachesuffix;

// check to see if this view is cached
if ( !TemplateCache.lookup( keyname ) )
{
// simulate some really slow code needed to render view
sleep( 5000 );
}

rc.incache = TemplateCache.lookup( keyname );
rc.cached = TemplateCache.getkeys();
rc.mykey = keyname;
arguments.event.setView( name=theview, cache="true", cacheTimeout="5", cacheLastAccessTimeout="2", cacheSuffix=cachesuffix );
}

This might look complicated but it really isn't. Firstly I'm getting a reference to the template cache and then calling the lookup method on it. If it returns true I know the view has been cached, if not I run the slow code. The only tricky bit is working out what the cache key name is, but I've done all the work for you :)

Now when I run index.cfm?event=general.templatecache, as expected the first call is slow, but subsequent calls are fast. The output is:


Layout Rendered: {ts '2010-12-07 15:03:07'}
view rendered: {ts '2010-12-07 15:00:50'}

Awesome!

As I stated before, these examples are based on a bleeding edge release of ColdBox (ColdBox 3.0.0 RC2-318-GENESIS-14:14), as Luis kindly fixed a bug I found in RC1.

I've also noticed (although this is not documented) that there is a new variable in the Private Request Collection called cbox_incomingcontexthash which can help when building the key.


/**
* @hint I cache the view not the layout and only run the model if required
**/

void function bleedingedge( required event )
{
var rc = arguments.event.getCollection();
var prc = arguments.event.getCollection( private=true );

// get the template cache
var TemplateCache = getColdboxOCM( "template" );
// set the view we want to render
var theview = "General/bleedingedge";
// construct the key name
var keyname = TemplateCache.getViewCacheKeyPrefix() & ":" & theview & prc.cbox_incomingcontexthash;

// check to see if this view is cached
if ( !TemplateCache.lookup( keyname ) )
{
// simulate some really slow code needed to render view
sleep( 5000 );
}

rc.incache = TemplateCache.lookup( keyname );
rc.cached = TemplateCache.getkeys();
rc.mykey = keyname;

arguments.event.setView( name=theview, cache="true", cacheTimeout="5", cacheLastAccessTimeout="2", cacheSuffix=prc.cbox_incomingcontexthash );
}

So there you go, I hope this is useful. More information is available in the Wiki:

http://wiki.coldbox.org/wiki/ColdboxCache.cfm#View_Caching

http://wiki.coldbox.org/wiki/ColdboxCache.cfm#Event_Caching


5 comments

  1. Very cool caching. Luis just keeps making this framework more and more robust.

    Comment by Ben Nadel – December 07, 2010
  2. Excellent post John. One thing I would point out is that you can make event caching unique by incorporating values into the request collection at the "onRequestCapture" interception point. The caching machinery will pickup up any values you add to the request collection after that interception point to create the event cachhing entries. This way you can make event caching unique to users and more.

    Comment by Luis Majano – December 07, 2010
  3. You've done a great job explaining these features. Keep it up!

    Comment by Aaron Greenlee – December 07, 2010
  4. @Ben - yeah, I'm pretty new to ColdBox, but there is soooo much cool stuff it does!

    @Luis - sounds interesting, I'll definately have a look at that.

    @Aaron - thanks :)

    Comment by John Whish – December 07, 2010
  5. This is great but what do if only want to cache the view and the content rendered is based on url variables passed in? View caching seems to handle that view as one object not different ones like the event cache does.

    Comment by Jonathan – September 29, 2011

Leave a comment

If you found this post useful, interesting or just plain wrong, let me know - I like feedback :)

Please note: If you haven't commented before, then your comments will be moderated before they are displayed.

Please subscribe me to any further comments
 

Search

Wish List

Found something helpful & want to say ’thanks‘? Then visit my Amazon Wish List :)

Categories

Recent Posts