WebGecko Home
Authoring and caching solutions for a better Web experience
SEARCHStart search

COMMUNITY

DATA CACHING BEST PRACTICES

WHITE PAPER: FAST COMMERCE SITES

DATA CACHING BEST PRACTICES

Author: John Crim
Updated 11/13/2002 10:42 AM

Summary: This article is for developers who are sold on caching, but want to know how to implement caching properly.  This article covers best practices for web developers using data caching.  Many of the guidelines apply whether using ASPCache or other caching objects.  These best practices will help developers avoid common mistakes and use data caching more effectively.

This article explains some best practices for data caching in your ASP applications.  Specifically, data caching with ASPCache is covered, though many of these principles also apply to caching with the ASP Application object.  These best practices are proven to yield the best web site performance, reduce bugs, and obtain the most reliable behavior.

A discussion on caching best practices is important, because many developers are used to thinking in terms of local variables that don't unexpectedly change, in a single-threaded environment.  The programming landscape changes when caching is introduced: ASPCache is a threadsafe COM object, which means it can be accessed simultaneously by any number of threads, without corrupting data.  This is a necessity because each ASP page executes in its own thread, and ASPCache provides data sharing across pages.  Another page can change the data at any point in a page's execution.  In addition, an item may be removed from the Cache at any time, either because the item has expired, or because it is under-used and is being cleaned up.  By applying the pointers in this article, your ASP application will behave as designed.

The best practices explained below are:

1) Store the cached data in a variable first
1a) Be careful when retrieving objects with VB/VBScript Set
2) Don't cache Apartment-Threaded COM objects
2a) Don't cache VB COM objects
2b) Don't cache JScript arrays or classes, or VBScript classes
2c) Do cache value types
2d) Do cache COM objects that are "Both" threaded, threadsafe, and written in C++
2e) Use Recordset.GetRows() to store Recordset data
3) When caching large amounts of data, enable the cleanup mechanisms
3a) Cache.FlushEnabled and Cache.FlushInterval must be set to enable background cleanup of expired and under-used items
4) Consider that 2 or more instances of an ASP page may be run simultaneously
5) Cache data in the most useful format possible
5a) If applicable, fragment caching is the most efficient form of caching
6) Turn debugging off
7) Be aware that the Session object serializes all requests in the same session

As an aside, many of these best practices apply even if you are not using ASPCache - for example, if you are using the ASP Application object to store shared data.  Specifically, best practices 2, 4, 5, 6, and 7 apply equally whether you are using ASPCache or the ASP Application object.

Best Practices Code Example

Let's start with a code example that applies several of these best practices.  This code shows a good data caching design pattern to use in your web apps.

In subsequent code, best practices are referenced using labels like BP#3, which stands for Best Practice #3.

ASPCache objects are always instantiated in the global.asa file - this is necessary to give the ASPCache object Application-wide scope.  Also, note that background cleanup (flushing) is enabled for the ASPCache object:

REM #######################################
REM   GLOBAL.ASA                          #
REM #######################################

<OBJECT RUNAT=Server SCOPE=Application ID=Cache PROGID=ASPCache></OBJECT>

<SCRIPT LANGUAGE=VBScript RUNAT=Server>

Option Explicit

Sub Application_OnStart

    ' BP#3) Enable the cleanup mechanisms (flushing)
    
    ' Enable background cleanup
    Cache.FlushEnabled = True
    
    ' Flush items that haven't been used in 10 minutes
    Cache.FlushTimeout = 600000
    
    ' Perform the background cleanup every 5 minutes
    Cache.FlushInterval = 30000
    
End Sub
</SCRIPT>

Next we look at an ASP page that uses cached data from a SQL Server database.  The Cache object refers to the ASPCache instance declared in global.asa.  The cached data expires every minute, which keeps the displayed data relatively fresh.

<!-- #include file="i_db.asp" -->

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD>
<TITLE>Cached Array From Pubs</TITLE>
</HEAD>
<BODY>
<H3>Cached Array From Pubs</H3>
<TABLE width="100%" border=1 bordercolor=gray cellspacing="0">
<tr><th>Title ID</th><th>Title</th><th>Price</th></tr>
<%

Function GetRecordset()
    ' Grab the recordset containing the data
    Dim rs
    Set rs = Server.CreateObject("ADODB.Recordset")
    rs.Open "SELECT title_id, title, price FROM titles", connStr
    Set GetRecordset = rs
End Function

Function GetCachedArray()
    
    ' Retrieve the cached array
    ' BP#1) Store the cached data in a variable first
    GetCachedArray = Cache("cachedArray")

    If IsEmpty(GetCachedArray) Then
        ' Item is not in the cache
        ' Create and cache the recordset data
        Dim rs
        Set rs = GetRecordset()

        ' Preferable to cache an array, not a recordset,
        ' Arrays are lighter, faster, and more agile than recordsets.
        ' BP#2d) Use Recordset.GetRows() to store Recordset data
        GetCachedArray = rs.GetRows()
        
        ' Add the array to the ASPCache collection
        ' Expire the item in 1 minute
        ' BP#3) Enable the cleanup mechanisms (expiration)
        ' BP#4) Be aware that Cache.Add may return false, if the item was
        ' already added
        Dim bAdded
        bAdded = Cache.Add("cachedArray", GetCachedArray, 60000)
        
        ' Display a message that the cache was refreshed
        If bAdded Then
            Response.Write "<div style=""color:red"">Cache Refreshed</div>"
        End If
    End If

End Function


' Retrieve the database array, either from the
' cache or directly from the DB.
Dim cachedArray
cachedArray = GetCachedArray()

' Display the array:

Dim nLastRow, iRow
nLastRow = UBound(cachedArray, 2)

' Loop through the array
For iRow = 0 To nLastRow
    ' Write out the array contents
    Response.Write "<tr><td>" & cachedArray(0, iRow) & "</td>"
    Response.Write "<td>" & cachedArray(1, iRow) & "</td>"
    Response.Write "<td>$" & cachedArray(2, iRow) & "</td></tr>" & vbCRLF
Next

%>
</TABLE>
</BODY></HTML>

Now let's discuss the data caching best practices and the reasons behind them:

1) Store the cached data in a variable first

This is a design pattern that ensures that the cached object will not be destroyed (removed) while you are working with it.  By setting a reference to the object first (a variable), you ensure that it won't be change or disappear at some random point in your code.

The best pattern for using cached data could be written in pseudo-code like this:

Var x = Cache.Read(key)
If (x Is Null) Then
    Build x the slow way
    Then store x in the Cache:
    Cache.Store(key, x)
End If

Now, use x however you want
x will not disappear on you

One way this pattern is implemented is by putting it in a function that returns the cached data.  In the code above, function GetCachedArray() implements this pattern.

Here is an example of how not to access items in the cache:

<H3>How not to use ASPCache</H3>
<%

If (Not Cache.Exists("key")) Then
    ' Create the cached data
    Dim rs, sDisplay
    Set rs = Server.CreateObject("ADODB.Recordset")
    rs.Open "SELECT title_id, title, price FROM titles", connStr
    
    ' This function takes the recordset and creates a string
    ' displaying the data - this can be a best practice if the data
    ' will only be displayed one way.
    sDisplay = FormatRecordset(rs)
    
    ' Store the data in the cache
    Cache("key") = sDisplay
    Cache.SetExpiration "key", 60000
    
    ' Display a message that the cache was refreshed
    Response.Write "<div style='color:red'>Cache Refreshed</div>"
End If
    
' Display the cached data
' This is bad! - the item could have been removed from the Cache after
' Cache.Exists() returned true.
%>
<DIV style="font-family:Verdana">
    
<% = Cache("key") %>
</DIV>

What is wrong with this code? Only one thing: The item in the cache labelled "key" may be present when Cache.Exists() is called, but may have been removed from the cache by the time Cache("key") is called.  If you are using the expiration feature of ASPCache, any expiring item can be removed when your code least expects it.  This is considered a race condition bug - it is timing dependent and will not occur very often, but can be difficult to find and debug.

The solution to this predicament is to set a variable to the cached data before you do anything else.  Since the variable resides in the page and not in the cache, its data will not change or go away unexpectedly.

1a) Be careful when retrieving objects with VB/VBScript Set

The following code checks whether an item in the cache is an object, and uses the VBScript Set keyword if it is an object.  What is wrong here?

Dim key, val
If IsObject(Cache(key)) Then
    Set val = Cache(key)
Else
    val = Cache(key)
End If

The lurking problem is due to a race condition.  The problem is that, if expiration is enabled, the item may expire after the IsObject call but before the Set val = Cache(key) line.  If this occurs, the Set line will raise an error, because Cache(key) will return Empty, and keyword Set raises an error if the right-hand side is not an object.

To get around this race condition/Set issue, I recommend adding a function like this:

' Gets a cached object or data
' Only necessary because of VBScript "Set"
' Returns False if value not present
Function GetCachedValue(key, ByRef val)
    If IsObject(Cache(key)) Then
        ' Error handling required due to possibility of race condition
        On Error Resume Next
        ' Get object
        Set val = Cache(key)
        If Err.number <> 0 Then
            GetCachedValue = False
        Else
            GetCachedValue = True
        End If
    Else
        ' Reading a regular variable
        val = Cache(key)
        If IsEmpty(val) Then
            GetCachedValue = False
        Else
            GetCachedValue = True
        End If
    End If        
End Function

This function returns False if the item doesn't exist, and True if the item exists.  Whether the item is an object or not, it is returned in the val parameter.  And lastly, this function handles the race condition where the item expires after the IsObject call but before the Set line.

The GetCachedValue() function can be used like:

Dim s, o
If GetCachedValue("string", s) Then
    %><p>Cache("string"): "<% =s %>"</p><%
Else
    
%><p>Cache("string") not present</p><%
End If

If GetCachedValue("oXml", o) Then
    
%><p>Cache("oXML").xml: "<% =Server.HTMLEncode(o.xml) %>"</p><%
Else
    
%><p>Cache("oXml") not present</p><%
End If

Note that a function like GetCachedValue() is not necessary in JScript.  This is because JScript does not require a keyword Set, and objects and values are set in the same way.

2) Don't cache Apartment-Threaded COM objects

Most COM objects are apartment-threaded - including all VB COM objects, and many other widely used COM objects (including ADO and the FileSystemObject).  If you store an apartment-threaded COM object in ASPCache (or in the ASP Application object), all access to that object will be marshalled to the original thread where the object was created.  This can drastically reduce the performance of your site - enough to negate the benefits of caching.

A quick explanation of apartment-threading: In COM, threads live in either a single-threaded apartment (STA) or in a multithreaded apartment (MTA).  Single-threaded apartments contain only one thread, and the multithreaded apartment contains all the threads in the process that are marked MTA.  Apartment-threaded COM objects can be created in an STA and invoked directly from that thread.  But all cross-apartment (cross-thread) calls to the object are marshalled through the Win32 message processing system.  Through this mechanism, all calls to objects in the STA are actually executed in the STA thread.  The advantage is that calls to STA objects are serialized, which avoids any thread safety issues.  The disadvantage is that performance is significantly degraded - in addition to the method call to message queue packaging/unpackaging overhead, all calls to any object in the STA are queued until all previous calls to objects in the STA have completed executing.

For more information on COM threading models and apartments, see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/aptnthrd_4a3w.asp.

In ASP, page code is executed in an STA thread.  This means that any apartment-threaded COM object created within the page can be invoked directly.  But if an apartment-threaded COM object is used that was created in another ASP page (in another STA), any calls to that object are cross-apartment calls, and will have to pass through the marshalling mechanism.

ASPCache supports the Both COM threading model, which means it can be accessed directly from STAs and from MTAs, without any marshalling.  The problem occurs if you store an apartment-threaded object in ASPCache - that object can only be invoked via STA marshalling.  So, avoid storing apartment-threaded COM objects in ASPCache.

Following are a few corollaries to this rule:

2a) Don't cache VB COM objects

All VB COM objects are apartment-threaded.  VB 6 is not capable of generating thread-safe code.  Therefore no VB COM objects should ever be stored in ASPCache.

2b) Don't cache JScript arrays or classes, or VBScript classes

JScript arrays are actually COM objects, as are JScript classes and VBScript classes.  These COM objects are not thread-safe and are apartment-threaded, so don't store them in ASPCache.

2c) Do cache value types

All value types, including numeric types, strings, and safe arrays (VB arrays), are safe to store in ASPCache.  They are not COM objects, and thus the threading rules and marshalling don't apply.

2d) Do cache COM objects that are "Both" threaded, threadsafe, and written in C++

The only COM objects that should be stored in ASPCache are those that are "both" threaded, that use locking to prevent simultaneous access to data members, and are written in C++.  The objects should also aggregate the Free-Threaded Marshaller.  "Both" threaded objects can be accessed directly from any ASP page without going through the apartment-threaded marshaller.

An example is that other instances of ASPCache can be stored within a parent ASPCache object.  This is an effective way of storing separate dictionaries within your application.  It can also be used to set different flushing and cleanup behavior for different sets of data.

2e) Use Recordset.GetRows() to store Recordset data

Using Recordset.GetRows() is a best practice for these reasons:

  1. Performance - avoids column name lookup by string
  2. Performance - avoids a number of IDispatch calls, which are significantly slower than reading from an array index
  3. Performance - the 2d safe array returned from Recordset.GetRows() is a value type.  It can be stored and read from ASPCache without any marshalling overhead.
  4. Performance - avoids the overhead of calling Recordset.Clone() each time the cached recordset is used.  This would be necessary so multiple ASP pages won't interfere with each other's read point.

A code snippet for reading database data and storing it in the cache:

' Retrieve the recordset data
Dim rs, rows
Set rs = Server.CreateObject("ADODB.Recordset")
rs.Open "SELECT title_id, title, price FROM titles", connStr
rows = rs.GetRows()

' Add the array to ASPCache
Dim bAdded
bAdded = Cache.Add("db_rows", rows, 60000)

This example code is useful for displaying the contents of the GetRows() array.  Note that since the values are retrieved using numeric indexes, the retrieval is much faster than rs("ColumnName").Value .

' Display the array:
Dim nLastRow, iRow
nLastRow = UBound(rows, 2)

' Loop through the array
For iRow = 0 To nLastRow
    ' Write out the array contents
    Response.Write "<tr><td>" & rows(0, iRow) & "</td>"
    Response.Write "<td>" & rows(1, iRow) & "</td>"
    Response.Write "<td>$" & rows(2, iRow) & "</td></tr>" & vbCRLF
Next

3) When caching large amounts of data, enable the cleanup mechanisms

ASPCache includes cleanup mechanisms for these reasons:

  1. To make it easy for the developer to specify the lifetime of cached data
  2. To limit the memory and resources consumed by cached data
  3. To ensure that the most-used items remain in the cache, while the least-used items are evicted from the cache

The computer science concept of caching means to store results so later identical calculations can be short-circuited.  Caching trades storage for performance short-cuts.  But there can be a point of diminishing returns: If no cleanup is enabled, and a large set of keys are used, it is possible that the amount of cached data can grow too large, resulting in excessive memory use, memory paging, and decreased performance.  This is why it is important to use cleanup mechanisms that optimize the memory vs. performance trade-off.

If a small set of keys are used, and there is no need for item expiration, your application may work best without any cleanup enabled.  But for applications that cache lots of data and have a large set of possible keys, it is critical to enable and then tune the cleanup mechanisms.

ASPCache includes three separate cleanup mechanisms.  Each cleanup mechanism operates independently of the others, so they can be used in any combination.  They are:

  • Expiration - Sets the maximum lifetime for an item in the cache.  Each item in the cache can have its own lifetime - this lifetime is absolute, not to be confused with sliding expiration.  When its lifetime is exceeded, the item can no longer be accessed in the cache.

    Expiration is enabled via Cache.Add() and Cache.SetExpiration():

    ' Add an item that expires after 1 minute
    Dim bAdded
    bAdded = Cache.Add("key", value, 60000)

    ' Extend the item's expiration to 2 minutes (from now)
    Cache.SetExpiration "key", 120000

  • Flushing - Flushing removes items that have not been used in a while.  The FlushTimeout property applies to all items in the cache - if the background cleanup thread finds an item that has not been accessed (read or written) in FlushTimeout milliseconds, the item is removed.

    Flushing is enabled by enabling the background cleanup thread, and then setting an appropriate FlushTimeout:

    ' This code would normally be placed in global.asa

    ' Enable the background cleanup thread
    Cache.FlushEnabled = true

    ' Have the cleanup run every 5 minutes
    Cache.FlushInterval = 300000

    ' Set so items that haven't been used in 10 minutes
    ' are removed (flushed).
    Cache.FlushTimeout = 600000

    This code tells the cleanup thread to run every 5 minutes.  If the cleanup thread finds an item that has not been accessed (read or written) in 10 minutes, that item is marked for removal.  Removal of all marked items occurs after scavenging is complete.  Because cleanup is not immediate, it is possible that an item could be unused for up to 15 minutes before it is removed.

    Note that the cleanup thread runs at below normal priority, so if the CPU is maxed out with ASP requests (which run at normal priority), the cleanup thread won't run until some unused cycles are available.  It is also important that the cleanup thread does not lock access to the Cache - it makes copies of the expiration and timestamp data, so the cleanup (scavenging) process operates independently of the cached data.  With these mechanisms in place, the background cleanup thread has virtually no impact on the performance of your ASP pages.

  • Shrinking - Shrinking lets the developer set a MaxSize for the cache.  If that capacity is exceeded, the least frequently used items are removed from the cache to reduce the Count to MaxSize.

    Shrinking can be enabled using this code:

    ' This code would normally be placed in global.asa

    ' Enable shrinking
    Cache.MaxSize = 5000

    When shrinking is enabled, the background cleanup thread is run any time the number of items in the cache exceeds the MaxSize.  The items with the longest TimeSinceLastUse are removed from the cache, to reduce the number of items to MaxSize.

3a) Cache.FlushEnabled and Cache.FlushInterval must be set to enable background cleanup of expired and under-used items

By default, ASPCache's background cleanup thread does not run.  This results in expired items not being removed until they are accessed after their expiration.  While this behavior honors the principle of absolute expiration, it does not provide optimal usage of memory and resources.  To enable background cleanup of expired items, the background cleanup thread must be enabled using the FlushEnabled and FlushInterval properties:

' This code would normally be placed in global.asa

' Enable the background cleanup thread
Cache.FlushEnabled = true

' Have the cleanup run every 5 minutes
Cache.FlushInterval = 300000

' Disable flushing, so rarely-used items aren't removed
Cache.FlushTimeout = 0

In this case, the names of the FlushEnabled and FlushInterval properties are misleading - they could be better named BackgroundCleanupEnabled and BackgroundCleanupInterval.  The background cleanup thread performs all 3 types of cleanup, but the thread must be enabled by setting FlushEnabled and FlushInterval.

Shrinking also occurs on the background cleanup thread.  However, FlushEnabled does not have to be set.  If the number of items in the cache ever exceeds MaxSize, the background cleanup thread is started, independent of FlushEnabled and FlushInterval.

4) Consider that 2 or more instances of an ASP page may be run simultaneously

On a web site, it is entirely possibly that 2 or more instances of the same page may be run simultaneously.  As long as each page has its own variables, then parallel execution will not affect the page's behavior.  As soon as you add any global or shared resources that may be shared between multiple pages or multiple instances of the same page, you have to consider what could happen.

For example, consider this code, which creates and stores data in the cache if the data is not already cached:

<%
Dim rows
rows = Cache("db_rows")

If IsEmpty(rows) Then
    ' Create the cached data
    Dim rs, rows
    Set rs = Server.CreateObject("ADODB.Recordset")
    rs.Open sQuery, connStr
    rows = rs.GetRows()

    ' Store the data in the cache
    Cache("db_rows") = rows
    Cache.SetExpiration "db_rows", 60000
    
    ' Display a message that the cache was refreshed
    Response.Write "<div style='color:red'>Cache Refreshed</div>"
End If

%>

There is nothing wrong with this example.  However, the developer should be aware that the If IsEmpty() block may be simultaneously executed by 2 page requests.  Thus the call to Cache("db_rows") = rows may be replacing a value that was just inserted there by another page, or another instance of the same page.  In addition, the call to Cache.SetExpiration() may be overwriting another value.  Even though there was no value in the cache when Cache("db_rows") was read, there may be a value there by the time Cache("db_rows") is written.  The developer should be sure that overwriting the value that was written by another page instance will not cause any undesirable side effects.

If the possibility of overwriting the value stored by another page instance is problematic, the developer should use the Cache.Add() method.  Cache.Add() will only add the key and value if the key is not already present in the cache - if will never overwrite an existing value.  Like all other ASPCache methods, Add() is atomic and threadsafe.  If the item is already present in the cache, Add() will not replace the value and will return false.

5) Cache data in the most useful format possible

The goal here is to cache your data in a format that is immediately useful when an ASP page reads the data from the cache.  It is based on the assumption that the cached data will be read more often than it is updated.  If this assumption is not true, then caching is not providing much value.

If your data has to be transformed into a certain format each time it is read from the cache, it would be more efficient to do that transformation work when you first store the data in the cache.  By minimizing the work required to use the cached data, you will significantly improve the performance of your web app.

For example, consider a situation where you store a Recordset array in the cache, and most of the time when the data is displayed it is sorted by the 2nd column.  By sorting by the 2nd column before the data is stored in the cache, you can skip the sorting work for many of the times the data is displayed.

Also, if a recordset is always displayed the same way, within a table or within some combo-box output, it is more efficient to cache the output string than to cache the data as an array, and then rebuild the output each time the page is viewed.  In contrast, if the data is frequently displayed differently, or if lookups are required, then caching a data array is more practical.

Database-driven, output cached, state-preserving drop-down list box

This next code example shows how to implement a drop-down list box that is populated from the database, output cached in ASPCache, and that preserves its selected value across requests.  Caching the list box's output is more efficient than re-creating it for each request.  The interesting part is how the selected attribute is inserted in the correct place before the output is displayed, to maintain continuity for the user.

This is a good example of caching data in the most useful format possible.  The most useful format for sharing across pages is the database populated list box HTML.  However, that output still needs to be tweaked before it can be displayed, so that the user's selection is preserved.

To use this database-driven, cached, state-preserving drop-down list box, all you have to do is include this code in your page:

DisplaySelectedDBDropDown "ListBoxName", "SELECT ID, Display FROM Table"

Here is the code for the list box implementation:

<%@ Language=VBScript %>
<% Option Explicit

' Global connection variable
Dim cnn

Sub OpenConn
    If Not IsObject(cnn) Then
        Set cnn=Server.CreateObject("ADODB.Connection")
        cnn.Open connStr
    End If
End Sub

Sub CloseConn
    If IsObject(cnn) Then
        cnn.Close
        Set cnn = Nothing
    End If
End Sub

'===============================================
' Function GetCachedDBDropDown
'   Parameters:
'     sDDLName  Name of the <select> control
'     sQuery    SQL query, first column is the
'               option value, 2nd col is option display
'
' Caches and returns an HTML string that displays
' a drop-down list box filled with DB data.
'===============================================

Function GetCachedDBDropDown (sDDLName, sQuery)
    ' Read from the cache first
    Dim sKey
    sKey = sDDLName & "DropDown"
    GetCachedDBDropDown = Cache(sKey)
    If Not IsEmpty(GetCachedDBDropDown) Then
        ' Found in cache, return it
        Exit Function
    End If
    
    ' Not cached, so build it
    
    Dim s, rst
    s = "<select name=""" & sDDLName & """>"
    OpenConn
    Set rst = cnn.Execute(sQuery)
  
    ' Use the ADO recordset to populate the dropdown list.
    Do Until rst.EOF
        s = s & "<option value=""" & rst(0) & """ >"
        s = s & rst(1) & "</option>"
        rst.MoveNext
    Loop    
    s = s & "</select>"
    
    ' Store the cached output string
    ' Expire in 1hr
    Cache.Add sKey, s, 3600000
    GetCachedDBDropDown = s
End Function


'===============================================
' Sub DisplaySelectedDBDropDown
'   Parameters:
'     sDDLName  Name of the <select> control
'     sQuery    SQL query, first column is the
'               option value, 2nd col is option display
'
' Displays a drop-down list box filled with DB data.
' Current item selected is preserved across requests.
' GetCachedDBDropDown is called, so the main contents
' of the dropdown are held in the Cache.
'===============================================

Sub DisplaySelectedDBDropDown (sDDLName, sQuery)
    ' Grab the cached dropdown list string
    Dim s
    s = GetCachedDBDropDown(sDDLName, sQuery)
    
    ' Determine which ID is selected
    Dim selID
    selID = Request(sDDLName)
    If IsEmpty(selID) Then
        ' Nothing selected, display the standard DDL
        Response.Write s
        Exit Sub
    End If
    
    ' Insert the 'selected' tag
    ' This is necessary so the user doesn't have to
    ' start over each time.
    Dim sSearch, i, splitPoint
    sSearch = "<option value=""" & selID & """"
    i = InStr(s, sSearch)
    If i = 0 Then
        ' No matching option found
        Response.Write s
        Exit Sub
    End If
    
    splitPoint = i + Len(sSearch)
    Response.Write Left(s, splitPoint - 1) & " selected" & Mid(s, splitPoint)
End Sub


'======================
' Page code starts here
Dim sCategoryQuery
sCategoryQuery = "SELECT CategoryID, CategoryName FROM Categories" _
                                & " ORDER BY CategoryName;"

%><html>
  <head>
    <title>Products optimized with ASPCache</title>
  </head>
  <body>
    <form action="Products_ach.asp" method="post" ID="Form1">
      Select a Category:

      
<% DisplaySelectedDBDropDown "Category", sCategoryQuery %>
      
      <input type="submit" value="Show Products">
      

5a) If applicable, fragment caching is the most efficient form of caching
If a section of content will be displayed identically for multiple users and/or multiple requests, we recommend using ASPCache's page fragment caching mechanism.  Output caching is much more efficient than caching data and rebuilding the output for each page view.  ASPCache's fragment caching implementation is further optimized to:
  • Avoid overhead from BSTR allocation and concatenation
  • Avoid text conversion to Unicode and back to ANSI
  • Avoid COM object creation (no registry hits)
  • Avoid repetitive file access
  • Avoid execution of script code and IDispatch calls

Thanks to all these optimizations, ASPCache's fragment caching mechanism is better than 3 times faster than caching strings and calling Response.Write() on the cached strings.  Our tests have shown ASP pages using ASPCache fragment caching can execute at over 1000 requests per second.

This code snippet shows how to use ASPCache's fragment caching:

' Display the page fragment for the specified product

Dim bFragWritten
bFragWritten = Cache.WriteFile("products/product" & pfid & ".htm")
If Not bFragWritten Then
    Call UnknownProduct( )
End If


Sub UnknownProduct( )
    Response.Write "<h3>Product cannot be found</h3>"
End Sub

The page fragment files can be programatically created and database driven.  We recommend using Active Page Generator for event-driven, programmable, file generation.  Typically events like a database change or posting a new article would dictate that the page fragment file be re-generated.  Custom code and XSL/T can also be used to generate the page fragments, but Active Page Generator tends to be more powerful and flexible.

6) Turn debugging off

When ASP debugging is enabled, all ASP requests execute in a single thread.  So, make sure you turn debugging off before running any performance tests or deploying to a live site.  Also, be aware that the robustness of your code with respect to thread-safety cannot be tested until debugging is turned off.

7) Be aware that the Session object serializes all requests in the same session

All requests using the same session cookie are serialized to the same thread.  This means you don't have to worry about locking or thread safety when using the Session object.  But, beware when testing: If your test clients all pass in the same SessionID cookie, all requests will be handled by the same ASP thread, so the test will not properly reflect the performance and scalability of your application.

Download the code for this article
 ach_best_practices.zip
Updated 8/26/2002 6:00 PM
Requirements:
  • ASPCache 1.1 or later
  • SQL Server 7.0 or later, with Pubs and Northwind databases installed



Home  |  Products  |  Community  |  Sales  |  Support  |  Company
Mailing List  |  Services  |  Press  |  Search  |  Privacy  |  Contact Us