Optimizing ColdFusion applications

You can optimize your ColdFusion application in many ways. Much of optimizing ColdFusion involves good development and coding practices. For example, good database design and usage is a prime contributor to efficient ColdFusion applications.

In several places, this book documents optimization techniques as part of the discussion of the related ColdFusion topic. This section provides information about general ColdFusion optimization tools and strategies, and particularly about using CFML caching tags for optimization. This section also contains information on optimizing database use, an important area for application optimization.

The ColdFusion MX Administrator provides caching options for ColdFusion pages and SQL queries. For information on these options, see the Administrator online Help and Administering ColdFusion MX.

For information on debugging techniques that can help you identify slow pages, see Chapter 18, "Debugging and Troubleshooting Applications".

For additional information on optimizing ColdFusion, see the Macromedia ColdFusion support center at http://www.macromedia.com/support/coldfusion.

Caching ColdFusion pages that change infrequently

Some ColdFusion pages produce output that changes infrequently. For example, you might have an application that extracts a vendor list from a database or produces a quarterly results summary. Normally, when ColdFusion gets a request for a page in the application, it does all the business logic and display processing required to produce the report or generate and display the list. If the results change infrequently, this can be an inefficient use of processor resources and bandwidth.

The cfcache tag tells ColdFusion to cache the HTML that results from processing a page request in a temporary file on the server. This HTML does not need to be generated each time the page is requested. When ColdFusion gets a request for a cached ColdFusion page, it retrieves the pregenerated HTML page without having to process the ColdFusion page. ColdFusion can also cache the page on the client. If the client browser has its own cached copy of the page from a previous viewing, ColdFusion instructs the browser to use the client's page rather than resending the page.

Note:   The cfcache tag caching mechanism considers each URL to be a separate page. Therefore, http://www.mySite.com/view.cfm?id=1 and http://www.mySite.com/view.cfm?id=2 result in two separate cached pages. Because ColdFusion caches a separate page for each unique set of URL parameters, the caching mechanism accommodates pages for which different parameters result in different output.

Using the cfcache tag

You tell ColdFusion to cache the page results by putting a cfcache tag on your ColdFusion page above code that outputs text. The tag lets you specify the following information:

You can also specify several attributes for accessing a cached page on the web server, including a user name and password (if required by the web server), the port, and the protocol (HTTP or HTTPS) to use to access the page.

Place the cfcache tag above any code on your page that generates output, typically at the top of the page body. For example, the following tag tells ColdFusion to cache the page on both the client and the server. On the server, the page is cached in the e:/temp/page_cache directory. ColdFusion retains the cached page for one day.

<cfcache timespan="#CreateTimespan(1, 0, 0, 0)#" directory="e:/temp/page_cache">

Caution:   If your Application.cfm page displays text; for example, if it includes a header page, use the cfcache tag on the Application.cfm page in addition to the pages that you cache. Otherwise, ColdFusion displays the Application.cfm page output twice on each cached page.

Flushing cached pages

ColdFusion automatically flushes any cached page if you change the code on the page. It also automatically flushes pages after the expiration timespan passes.

You can use the cfcache tag with the action="flush" attribute to immediately flush one or more cached pages. You can optionally specify the directory that contains the cached pages to be flushed and a URL pattern that identifies the pages to flush. If you do not specify a URL pattern, all pages in the directory are flushed. The URL pattern can include asterisk (*) wildcards to specify parts of the URL that can vary.

When you use the cfcache tag to flush cached pages, ColdFusion deletes the pages cached on the server. If a flushed page is cached on the client system, it is deleted, and a new copy gets cached, the next time the client tries to access the ColdFusion page.

The following example flushes all the pages in the e:/temp/page_cache/monthly directory that start with HR:

<cfcache action="flush" directory="e:/temp/page_cache/monthly" expirURL="HR*">

If you have a ColdFusion page that updates data you use in cached pages, the page that does the updating includes a cfcache tag that flushes all pages that use the data.

For more information on the cfcache tag, see CFML Reference.

Caching parts of ColdFusion pages

In some cases, your ColdFusion page might contain a combination of dynamic information that ColdFusion must generate each time it displays the page, and parts it generates dynamically, but that change less frequently. In this case, you cannot use the cfcache tag to cache the entire page. Instead, use the cfsavecontent tag to cache the infrequently changed content.

The cfsavecontent tag saves the results of processing the tag body in a variable. For example, if the body of the cfsavecontent tag contains a cfexecute tag that runs an executable program that displays data, the variable saves the output.

You can use the cfsavecontent tag to cache infrequently changing output in a shared scope variable. If the information is used throughout the application, save the output in the Application scope. If the information is client-specific, use the Session scope. Because of the overhead of locking shared scope variables, use this technique only if the processing overhead of generating the output is substantial.

Before you use this technique, also consider whether other techniques are more appropriate. For example, query caching eliminates the need to repeat a common query. However, if the effort of processing the data or in formatting the output is substantial, using the cfsavecontent tag can save processing time.

Using this technique, if the variable exists, the page uses the cached output. If the variable does not exist, the page gets the data, generates the output, and saves the results to the shared scope variable.

The following example shows this technique. It has two parts. The first part welcomes the user and prints out a random lucky number. This part runs and produces a different number each time a user opens the page. The second part performs a database query to get information that changes infrequently, in this case a listing of the current special sale items. It uses the cfsavecontent tag to get the data only when needed.

Tip:   If you use this technique frequently, consider incorporating it in a custom CFML tag.

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

<!--- Set a flag to indicate whether the Application scope variable exists --->
<cflock scope="application" timeout="20" type="readonly">
  <cfset IsCached = Not IsDefined("Application.ProductCache")>
</cflock>

<!--- If the flag is false, query the DB, and save an image of
  the results output to a variable --->
<cfif not IsCached>
  <cfsavecontent variable="ProductCache">
  <!--- Perform database query --->
    <cfquery dataSource="ProductInfo" name="specialQuery">
      SELECT ItemName, Item_link, Description, BasePrice
      FROM SaleProducts
    </cfquery>
<!--- Calculate sale price and display the results --->
    <h2>Check out the following specials</h2>
    <table>
    <cfoutput query="specialQuery">
      <cfset salePrice= BasePrice * .8>
      <tr>
        <td>#ItemNAme#</td>
        <td>#Item_Link#</td>
        <td>#Description#</td>
        <td>#salePrice#</td>
      </tr>
    </cfoutput>
    </table>
  </cfsavecontent>

<!--- Save the results in the Applicaiton scope --->
  <cflock scope="Application" type="Exclusive" timeout=30>
    <cfset Application.productCache = ProductCache>
  </cflock>
</cfif>

<!--- Use the Application scope variable to display the sale items --->
<cflock scope="application" timeout="20" type="readonly">
  <cfoutput>#Application.ProductCache#</cfoutput>
</cflock>

Reviewing the code

The following table describes the code and its function:
Code
Description
<cfoutput>
  Welcome to our home page.<br>
  The time is #TimeFormat(Now())#.<br>
  Your lucky number is:
    #RandRange(1,1000)#<br>
  <hr><br>
</cfoutput>
Displays the part of the page that must change each time.
<cflock scope="application" timeout="20"
    type="readonly">
  <cfset IsCached = IsDefined
    ("Application.ProductCache")>
</cflock>
Inside a read-only lock, tests to see if the part of the page that changes infrequently is already cached in the Application scope, and sets a boolean flag variable with the result.
<cfif not IsCached>
  <cfsavecontent variable="ProductCache">
If the flag is False, uses a cfsavecontent tag to save output in a Variables scope variable. Using the Variables scope eliminates the need to do a query (which can take a long time) in an Application scope lock.
<cfquery dataSource="ProductInfo" 
    name="specialQuery">
  SELECT ItemName, Item_link, 
    Description, BasePrice
  FROM SaleProducts
</cfquery>
Queries the database to get the necessary information
<h2>Check out the following specials</h2>
<table>
  <cfoutput query="specialQuery">
    <cfset salePrice = BasePrice * .8>
    <tr>
      <td>#ItemNAme#</td>
      <td>#Item_Link#</td>
      <td>#Description#</td>
      <td>#salePrice#</td>
    </tr>
  </cfoutput>
  </table>
Displays the sale items in a table. Inside a cfoutput tag, calculates each item's sale price and displays the item information in a table row.
Because this code is inside a cfsavecontent tag, ColdFusion does not display the results of the cfoutput tag. Instead, it saves the formatted output as HTML and text in the ProductCache variable.
</cfsavecontent>
Ends the cfsavecontent tag block.
<cflock scope="Application" type="Exclusive"
    timeout=30>
  <cfset Application.productCache =
    productcache>
</cflock>
Inside an Exclusive cflock tag, saves the contents of the local variable ProductCache in the Application scope variable Application.productCache.
</cfif>
Ends the code that executes only if the Application.productCache variable does not exist.
<cflock scope="application" timeout="20"
    type="readonly">
  <cfoutput>#Application.ProductCache#</cfoutput>
</cflock>
Inside a cflock tag, displays the contents of the Application.productCache variable.

Optimizing database use

Two important ColdFusion MX tools for optimizing your use of databases are the cfstoredproc tag and the cfquery tag cachedWithin attribute.

Note:   Poor database design and incorrect or inefficient use of the database are among the most common causes of inefficient applications. Consider the different methods that are available for using databases and information from databases when you design your application. For example, if you need to average the price of a number of products from an SQL query, it is more efficient to use SQL to get the average than to use a loop in ColdFusion.

Using stored procedures

The cfstoredproc tag lets ColdFusion MX use stored procedures in you database management system. A stored procedure is a sequence of SQL statements that is assigned a name, compiled, and stored in the database system. Stored procedures can encapsulate programming logic in SQL statements, and database systems are optimized to execute stored procedures efficiently. As a result, stored procedures are faster than cfquery tags.

You use the cfprocparam tag to send parameters to the stored procedure, and the cfproresult tag to get the record sets that the stored procedure returns.

The following example executes a Sybase stored procedure that returns three result sets, two of which the example uses. The stored procedure returns the status code and one output parameter, which the example displays.

<!--- cfstoredproc tag --->
<cfstoredproc procedure = "foo_proc" dataSource = "MY_SYBASE_TEST" 
  username = "sa" password = "" returnCode = "Yes">

  <!--- cfprocresult tags --->
  <cfprocresult name = RS1>
  <cfprocresult name = RS3 resultSet = 3>

  <!--- cfprocparam tags --->
  <cfprocparam type = "IN"
    CFSQLType = CF_SQL_INTEGER
      value = "1"   dbVarName = @param1>
  <cfprocparam type = "OUT"  CFSQLType = CF_SQL_DATE
    variable = FOO dbVarName = @param2>
<!--- Close the cfstoredproc tag --->
</cfstoredproc>

<cfoutput>
  The output param value: '#foo#'<br>
</cfoutput>

<h3>The Results Information</h3>
<cfoutput query = RS1>
  #name#,#DATE_COL#<br>
</cfoutput>
<br>
<cfoutput>
  <hr>
  Record Count: #RS1.recordCount#<br>
  Columns: #RS1.columnList#<br>
  <hr>
</cfoutput> 

<cfoutput query = RS3>
  #col1#,#col2#,#col3#<br>
</cfoutput>
<br>
<cfoutput>
  <hr><br>
  Record Count: #RS3.recordCount#<br>
  Columns: #RS3.columnList#<br>
  <hr>

  The return code for the stored procedure is: '#cfstoredproc.statusCode#'<br>
</cfoutput>

Reviewing the code

The following table describes the code and its function:
Code
Description
<cfstoredproc procedure = "foo_proc" 
  dataSource = "MY_SYBASE_TEST" username = "sa"
  password = "" returnCode = "Yes">
Runs the stored procedure foo_proc on the MY_SYBASE_TEST data source. Populates the cfstoredproc. statusCode variable with the status code returned by stored procedure.
<cfprocresult name = RS1>
<cfprocresult name = RS3 resultSet = 3>
Gets two record sets from the stored procedure: the first and third result sets it returns.
<cfprocparam type = "IN"
  CFSQLType = CF_SQL_INTEGER
    value = "1" dbVarName = @param1>
<cfprocparam type = "OUT" CFSQLType = CF_SQL_DATE
  variable = FOO dbVarName = @param2>
</cfstoredproc>
Specifies two parameters for the stored procedure, an input parameter and an output parameter. Sets the input parameter to 1 and the ColdFusion variable that gets the output to FOO.
Ends the cfstoredproc tag body.
<cfoutput>
  The output param value: '#foo#'<br>
</cfoutput>

<h3>The Results Information</h3>
<cfoutput query = RS1>
  #name#,#DATE_COL#<br>
</cfoutput>
<br>

<cfoutput>
  <hr>
  Record Count: #RS1.recordCount#<br>
  Columns: #RS1.columnList#<br>
  <hr>
</cfoutput> 

<cfoutput query = RS3>
  #col1#,#col2#,#col3#<br>
</cfoutput>
<br>

<cfoutput>
  <hr><br>
  Record Count: #RS3.recordCount#<br>
  Columns: #RS3.columnList#<br>
  <hr>
  The return code for the stored procedure is:
  '#cfstoredproc.statusCode#'<br>
</cfoutput>
Displays the results of running the stored procedure:
  • The output parameter value,
  • The contents of the two columns in the first record set identified by the name and DATE_COL variables. You set the values of these variables elsewhere on the page.
  • The number of rows and the names of the columns in the first record set
  • The contents of the columns in the other record set identified by the col1, col2, and col3 variables.
  • The number of rows and the names of the columns in the record set
  • The status value returned by the stored procedure.

For more information on creating stored procedures, see your database management software documentation. For more information on using the cfstoredproc tag, see CFML Reference.

Using the cfquery tag cachedWithin attribute

The cfquery tag cachedWithin attribute tells ColdFusion to save the results of a database query for a specific period of time. This way, ColdFusion accesses the database on the first page request, and does not query the database on further requests until the specified time expires. Using the cachedWithin attribute can significantly limit the overhead of accessing databases that do not change rapidly.

This technique is useful if the database contents only change at specific, known, times. or if the database does not change frequently and the purpose of the query does not require absolutely up to date results

You must use the CreateTimeSpan function to specify the cachedWithin attribute value (in days, hours, minutes, seconds format). For example, the following code caches the results of getting the contents of the Employees table of the CompanyInfo data source for one hour.

<cfquery datasource="CompanyInfo" name="master"
  cachedWithin=#CreateTimeSpan(0,1,0,0)#>
  SELECT * FROM Employees
</cfquery>

Providing visual feedback to the user

If an application might take a while to process data, it is useful to provide visual feedback to indicate that something is happening so the user does not assume that there is a problem and request the page again. Although doing this does not optimize your application's processing efficiency, it does make the application appear more responsive.

You can use the cfflush tag to return partial data to a user, as shown in Chapter 26, "Retrieving and Formatting Data".

You can also use the cfflush tag to create a progress bar. For information on this technique, see the technical article "Understanding Progress Meters in ColdFusion 5" at http://www.macromedia.com/v1/handlers/index.cfm?id=21216&method=full. (Although this article was written for ColdFusion 5, it also applies to ColdFusion MX.)

Comments