Accessibility
 
 
Simple Caching Techniques with CFSAVECONTENT

By Ray Camden
Principal Spectra Compliance Engineer
Macromedia, Inc.

Click here to download the article and code.

If you've ever had to deal with dynamic Web pages, you know that sometimes the results can take a while to process. Whether you use complex SQL queries or other techniques to get data out, actually generating the resulting page may be a slow process. Out of the box, ColdFusion ships with numerous tools that you can use to help find slow templates and make them quicker. These caching techniques have drawbacks, however. You can only cache an entire page, not a section. In this article, I will describe a simple caching system that you can add to your pages that will allow for both cached and dynamic content to peacefully coexist!

The CFCACHE Tag

As you can imagine, the CFCACHE tag allows you to cache a page. It takes all of two seconds to add to a template and it can immediately speed up the delivery of your page. Unfortunately, this tag only works if you want to cache an entire page. Consider listing one.

Listing 1:

<!--- Display the current time --->
<CFOUTPUT>The current time is #TimeFormat(Now())#</CFOUTPUT>

<!--- Do something slow --->
<CFLOOP INDEX="X" FROM=1 TO=990000></CFLOOP>

This simple script outputs the current time and does nothing else. The comment on the last line implies, however, that we have additional code that will slow down the delivery of the template. Now, imagine if we wanted to cache that code so that after it's run the first time, it doesn't need to run again. Your first guess may be to add the CFCACHE tag. But, once we do that, the entire page becomes cached. This means that the first time someone hits the page, they will see the current time. But every hit after that will show the time when the cache was created, not the current time.

What we need is a quick and easy way to cache a section of a page. That way the remainder of the page can stay dynamic while the slow part (or slow parts) can be run once and then stored into the server's RAM.

Enter CFSAVECONTENT

The tool we will use is CFSAVECONTENT. This tag, introduced in ColdFusion 5, allows you to wrap a block of output and save the result to a variable. Listing two contains a simple example of this tag in action.

Listing 2:

<CFSAVECONTENT VARIABLE="Services">
	<CFEXECUTE NAME="net.exe" ARGUMENTS="start" TIMEOUT="30"/>
</CFSAVECONTENT>

<CFOUTPUT>
	The current running processes are:

	#Services#
	
</CFOUTPUT>

In this example, we use CFSAVECONTENT to wrap a CFEXECUTE tag. As you may or may not know, CFEXECUTE calls command line programs. It can either display the result on screen or save it to a file. By using CFSAVECONTENT, I can take the result and place it into a variable called services. In listing 2, we wrap this variable with a PRE tag and output it to the screen. We could have done other things like strip out certain characters or reformat the output. As you can see, this is a pretty nice tool to have and is one of the more cool features of ColdFusion 5.

So, now that you have a basic understanding of CFSAVECONTENT, let's see how we can use it to add caching to a page. We will begin by examining listing 3. This template is our home page and it has a very slow portion in the center.

Listing 3:

<!--- Greet the user --->
<CFOUTPUT>
Welcome to our home page. The current time is #TimeFormat(Now())#.
Your random lucky number is: #RandRange(1,1000)#

</CFOUTPUT> <!--- Perform database query and then make product recommendation ---> <!--- Fake code here ---> <CFLOOP INDEX="X" FROM=1 TO=100000> <CFSET A = (X * 2)/X> <CFSET B = (X-200)+(X-50)> </CFLOOP> <CFOUTPUT> We recommend product ID #B#

</CFOUTPUT> <!--- Stock data, again, fake ---> <CFSET StockPrice = RandRange(10,30)> <!--- DotCom Company Penalty ---> <CFSET StockPrice = StockPrice - 6> <CFOUTPUT>The current price of our stock (ACME) is: #StockPrice#</CFOUTPUT>

This template contains three parts. The first section welcomes the user and prints out a random "lucky" number for him or her. The second section simply loops for about four to five seconds. The purpose of this block of code is to simulate a slow database query or some other logic that takes a lot of time. Lastly, we display the stock quote for our make-believe company. As we stated before, this code would certainly look different in the "real" world.

Now, the important thing to remember here is that we have three distinct parts of the page. The first and third need to be dynamic. The user's lucky number and the current stock price will fluctuate all day, so we can't cache that data. However, the middle part, which is slow, certainly cries out to be cached. In this case, it's a simple product recommendation, so caching it will certainly not be a big deal. If you run this template a few times in your browser you will notice how slow it is. Let's look at how a simple modification using CFSAVECONTENT can quickly cache this section.

Listing 4:

<!--- Greet the user --->
<CFOUTPUT>
Welcome to our home page. The current time is #TimeFormat(Now())#.
Your random lucky number is: #RandRange(1,1000)#

</CFOUTPUT> <CFIF NOT IsDefined("Application.ProductCache")> <CFSAVECONTENT VARIABLE="ProductCache"> <!--- Perform database query and then make product recommendation ---> <!--- Fake code here ---> <CFLOOP INDEX="X" FROM=1 TO=1000> <CFSET A = X * 2> <CFSET B = (X-200)+(X-50)> </CFLOOP> <CFOUTPUT> We recommend product ID #B#

</CFOUTPUT> </CFSAVECONTENT> <CFLOCK SCOPE="Application" TYPE="Exclusive" TIMEOUT=30> <CFSET Application.ProductCache = ProductCache> </CFLOCK> </CFIF> <CFOUTPUT>#Application.ProductCache#</CFOUTPUT> <!--- Stock data, again, fake ---> <CFSET StockPrice = RandRange(10,30)> <!--- DotCom Company Penalty ---> <CFSET StockPrice = StockPrice - 6> <CFOUTPUT>The current price of our stock (ACME) is: #StockPrice#</CFOUTPUT>

Let's take a look at the code that has changed. We begin by checking to see if Application.ProductCache is defined. This is an application variable that will store our cached content. If it doesn't exist, we run the same block of code we did before, except this time we wrap it with the CFSAVECONTENT tag. We use the tag to store the result into a variable. We then copy that variable to application scope inside of a lock tag. Outside of this CFIF block, we simply output the value of the application variable. Run this script in your browser (be sure to copy the Application.cfm file from the zip). On the first hit, the page will load as slowly as before, but on the second and every other hit, the page will display very quickly. We added about five to six lines of code, but have added an extremely powerful tool to our coding arsenal. (The code in listing four is not 100% lock safe. We are not locking the reads from the application scope. For an example that is "lock anal", see listing4_better.cfm in the zip file.) Next you may ask, well, what if the cached content can be cached, but only on a user by user basis. Simple. Just save the content to the session scope instead of the application scope! That change would take another three or four seconds.

Conclusion

In my previous article Understanding Progress Meters in ColdFusion 5, I discussed how CFFLUSH can be used to increase the apparent speed of a page. Once again we see how a little of ColdFusion code can go a long away to making your pages load quicker. It should be noted that the first thing you do before using these techniques is to carefully examine your code and make sure there isn't anything you can do to speed it up. But if those methods fail, it is nice to know that ColdFusion 5 has these features that can quickly be implemented within your Web application.