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:
</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.