Share via


Caching Architecture Guide for .NET Framework Applications

 

patterns and practices home

Appendix

Summary: This appendix includes a glossary of terms used in the guide and samples to support the other chapters.

This chapter includes the following sections:

  • "Appendix 1: Understanding State Terminology"
  • "Appendix 2: Using Caching Samples"
  • "Appendix 3: Reviewing Performance Data"

Appendix 1: Understanding State Terminology

This appendix provides descriptions and examples of the following terms associated with state, as overviewed in Chapter 1, "Understanding Caching Concepts":

  • Lifetime of state
  • Scope of state, including:
    • Physical scope
    • Logical scope

State refers to data, and the condition of that data, being used in a system at a certain point in time. The lifetime and scope of state need to be taken into account when you are caching state, because the cache must be accessible for the same period of time and from the same locations as the original state.

Understanding the Lifetime of State

The lifetime of state refers to the time during which that state is valid—that is, from when it is created to when it is removed. Table 7.1 shows examples of lifetime durations.

Table 7.1: State lifetime

Lifetime of state Duration of validity Example
Permanent Always valid. Data that is used across the application and exists beyond the lifetime of the application and its processes. Data stored in a database, file system, or any other durable medium.
Process (atomic transaction) Valid only during the lifetime of that transaction. Product selection process for adding items to a shopping basket in an online store.
Process (long-running transaction) Valid across all the messages involved in the transaction. Purchase data for an order used during a checkout conversation between client and server.
Session Specific to a particular user in a particular session and is valid only during that interactive user session. The shopping basket created during an e-commerce session.
Message The information that is passed between communicating services. Valid between creating the data and sending it on the one side and between receiving the message and processing the message data on the other. Purchase order document submitted to a Web service from a client browser.

In addition to considering the duration of the validity of the state, you also need to consider where the state can be accessed from.

Understanding the Scope of State

Scope of state refers to the accessibility of the applications state, whether it is the physical locations or the logical locations.

Understanding Physical Scope

Physical scope refers to the physical locations from which the state can be accessed. Table 7.2 shows examples of physical scope.

Table 7.2: Physical scope

Physical scope Locations of accessibility Example
Organization Can be accessed from any application in an organization Active Directory information, Exchange Server Global Address List (GAL)
Farm Shared between computers within an application farm Hit count on a Web site
Machine Shared amongst all applications or processes of an application on one computer Registry data
Process Shared across multiple AppDomains in the same process Authentication tokens
AppDomain Accessible only within the boundaries of an application domain AppDomain parameters

Understanding Logical Scope

Logical scope refers to the logical locations from which the state can be accessed. Table 7.3 shows examples of logical scope.

Table 7.3: Logical scope

Logical scope Locations of accessibility Example
Application Can be accessed only within a certain application Product catalog for online site, which may be different from the catalog available to business-to-business partners
Business process Available to a logical business process Checkout process from an online shop
Role Available to a subset of users/callers of an application Members of the management role accessing employee salary information
User Available to only one logged user Personal data, news page, and session page

Understanding your application's state and its characteristics, such as lifetime and scope, is important for planning your caching policies and mechanisms.

Appendix 2: Using Caching Samples

The following samples demonstrate aspects of caching that preceding chapters describe. They show advanced details of ways to load and expire data in your cache.

The first sample shows how to implement a cache notification system for expiring data in a cache and loading new data into a cache using SQL Server 2000 Notification Services. This is one of the loading systems discussed in Chapter 5, "Managing the Contents of a Cache."

The second sample shows how to develop an extended format time expiration using Microsoft Visual C#® .NET development tool. You can use this code to create a recurring absolute time expiration policy. This was introduced in Chapter 5, "Managing the Contents of a Cache."

Implementing a Cache Notification System

This sample supplements the earlier discussion of SQL Server 2000 Notification Services. It implements a flight schedule Web application that uses data from an XML Web service. The flow of the application is demonstrated in Figure 7.1.

Ee957910.f07cac01(en-us,MSDN.10).gif

Figure 7.1. Flight schedule application flow

The flow of data within the applications follows these steps:

  1. The application looks for flight schedule data in the cache. If it's there, the data is returned and displayed.
  2. If the data is not in the cache, the application retrieves the data from the Web service.
  3. The data is stored in the cache for future requests.

The problem with this scenario is that if the flight schedule in the Web service changes, the change is not reflected in the cache, and the Web application displays obsolete data.

To solve this problem, you can implement a notification mechanism using SQL Server Notification Services to notify the cache of flight schedule changes. The flow of the application after implementing this mechanism is shown in Figure 7.2.

Ee957910.f07cac02(en-us,MSDN.10).gif

Figure 7.2. Flight schedule application flow when using SQL Server 2000 Notification Services

The flow of data in the application now follows a different set of steps:

  1. The application looks for the flight schedule data in the cache. If it's there, the data is returned and displayed.
  2. If the data is not in the cache, the Web application retrieves the data from the Web service and instructs the cache to store the data. The cache stores the data and subscribes to a FlightsNotification event in Notification Services.
  3. Notification Services monitors the Web service for flight schedule changes.
  4. When a change occurs in the Web service, Notification Services informs the cache, which invalidates the obsolete item and then removes it.

Using Notification Services ensures that any changes to schedules are reflected in the Web application in a timely manner.

The full code sample for the flight schedule application can be downloaded from the Patterns and Practices Caching Architecture Guide workspace at GotDotNet.

For more information about Notification Services, see the SQL Server 2000 Notification Services Web site at https://msdn.microsoft.com/en-us/sqlserver/bb671178.aspx.

Implementing an Extended Format Time Expiration Algorithm

The following sample shows an implementation of absolute time, recurrent expiration algorithm. The extended format expiration algorithm allows you to define the lifetime of an item by specifying the expiration policy as a recurrent absolute time. It also enables you to define the lifetime of an item by specifying the day of week or the day of month when an item should expire.

using System;

/*
  minute       - 0-59
  hour         - 0-23
  day of month - 1-31
  month        - 1-12
  day of week  - 0-7 (Sunday is 0 or 7)
  wildcards    - * means run every

  Examples:
  5 * * * *    - run 5th minute of every hour
  5,31 * * * * - run every 5th and 31st minute of every hour
  1 21 * * *   - run every minute of the 21st hour of every day
  1 15,21 * * *- run every minute of the 15th and the 21st hour of every 
day
  31 15 * * *  - run 3:31 PM every day
  7 4 * * 6    - run Saturday 4:07 AM
  15 21 4 7 *  - run 9:15 PM on 4 July
*/

namespace ExtendedFormat
{
  class Test
  {
    /// <summary>
    /// The main entry point for the application
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
      Console.WriteLine( 
        ExtendedFormat.IsExtendedExpired( 
          "0 15,22 * * *", 
          new DateTime( 2002, 4, 22, 16, 0, 0 ), 
          new DateTime( 2002, 4, 22, 22, 0, 0 )  ));
    }
  }

  public class ExtendedFormat
  {
    /// <summary>
    /// Test the extended format with a given date.
    /// </summary>
    /// <param name="format">The extended format string</param>
    /// <param name="getTime">The time when the item has been
                              refreshed</param>
    /// <param name="nowTime">DateTime.Now, or the date to test 
                              with</param>
    /// <returns></returns>
    public static bool IsExtendedExpired( string format, 
                                   DateTime getTime, DateTime nowTime )
    {
      //Validate arguments
      if( format == null )
        throw new ArgumentNullException( "format" );

      string[] fmtarr = format.Split( ' ' );
      if( fmtarr.Length != 5 )
        throw new ArgumentException("Invalid format (" + 
                                    format + ")", "format");

      return ( ValidateMinute( fmtarr[ 0 ], getTime, nowTime ) &&
       ValidateHour( fmtarr[ 1 ], getTime, nowTime ) &&
       ValidateDayOfMonth( fmtarr[ 2 ], getTime, nowTime ) &&
       ValidateMonth( fmtarr[ 3 ], getTime, nowTime ) &&
       ValidateDayOfWeek( fmtarr[ 4 ], getTime, nowTime ) );
    }

    public static bool ValidateMinute( string formatItem, 
                                       DateTime getTime, 
                                       DateTime nowTime )
    {
      if( formatItem.IndexOf( "*" ) != -1 )
      {
        return true;
      }
      else
      {
        TimeSpan ts;
        int minute;
        string[] minutes = formatItem.Split( ',' );
        foreach( string sminutes in minutes )
        {
          minute = int.Parse( sminutes );
          ts = nowTime.Subtract( getTime );
          if( ts.TotalMinutes >= 60 )
          {
            return true;
          }
          else 
          {
    if( ( getTime.Minute < minute && nowTime.Minute >= minute ) || 
        ( getTime.Hour != nowTime.Hour && nowTime.Minute >= minute ) ||
    ( getTime.Hour != nowTime.Hour && getTime.Minute < minute ) ) 
             {
              return true;
            }
          }
        }
        return false;
      }
    }

    public static bool ValidateHour( string formatItem, 
                                     DateTime getTime, 
                                     DateTime nowTime )
    {
      if( formatItem.IndexOf( "*" ) != -1 )
      {
        return true;
      }
      else
      {
        TimeSpan ts;
        int hour;
        string[] hours = formatItem.Split( ',' );
        foreach( string shour in hours )
        {
          hour = int.Parse( shour );
          ts = nowTime.Subtract( getTime );
          if( ts.TotalHours >= 24 )
          {
            return true;
          }
          else 
     if( ( getTime.Hour < hour && nowTime.Hour >= hour ) || 
         ( nowTime.Day != getTime.Day && nowTime.Hour >= hour ) || 
         ( getTime.Day != nowTime.Day && getTime.Hour < hour ) )
          {
            return true;
          }
        }
        return false;
      }
    }

    public static bool ValidateDayOfMonth( string formatItem, 
                                           DateTime getTime, 
                                           DateTime nowTime )
    {
      if( formatItem.IndexOf( "*" ) != -1 )
      {
        return true;
      }      
      else
      {
        TimeSpan ts;
        int day;
        string[] days = formatItem.Split( ',' );
        foreach( string sday in days )
        {
          day = int.Parse( sday );
          ts = nowTime.Subtract( getTime );
          if( ts.TotalDays >= 30 )
          {
            return true;
          }
          else 
          {
      if( ( getTime.Day < day && nowTime.Day >= day ) || 
          ( getTime.Month != nowTime.Month && nowTime.Day >= day ) || 
          ( getTime.Month != nowTime.Month && getTime.Day < day ))
            {
              return true;
            }        
          }
        }
        return false;
      }
    }

    public static bool ValidateMonth( string formatItem, 
                                      DateTime getTime, 
                                      DateTime nowTime )
    {
      if( formatItem.IndexOf( "*" ) != -1 )
      {
        return true;
      }
      else
      {
        TimeSpan ts;
        int month;
        string[] months = formatItem.Split( ',' );
        foreach( string smonth in months )
        {
          month = int.Parse( smonth );
          ts = nowTime.Subtract( getTime );
          if( ts.TotalDays >= 365 )
          {
            return true;
          }
          else
          {
    if( ( getTime.Month < month && nowTime.Month >= month ) ||
        ( nowTime.Year != getTime.Year && nowTime.Month >= month ) || 
        ( getTime.Year != nowTime.Year && getTime.Month < month  ))
            {
              return true;
            }        
          }
        }
        return false;
      }
    }

    public static bool ValidateDayOfWeek( string formatItem, 
                                          DateTime getTime, 
                                          DateTime nowTime )
    {
      if( formatItem.IndexOf( "*" ) != -1 )
      {
        return true;
      }
      else
      {
        DateTime dt;
        DayOfWeek weekday;
        TimeSpan ts;

        string[] days = formatItem.Split( ',' );
        foreach( string sday in days )
        {
          weekday = (DayOfWeek)Enum.Parse( 
                                      typeof(DayOfWeek), sday, true );
          ts = nowTime.Subtract( getTime );
          if( ts.TotalDays >= 7 )
          {
            return true;
          }
          else
          {
            dt = nowTime;
            for( int i = 0; getTime < dt; i++ )
            {
              dt = dt.AddDays( -1 );
              if( dt.DayOfWeek == weekday )
              {
                return true;
              }
            }
            return false;
          }
        }
        return false;
      }
    }
  }
}
  

This code allows you to specify your time expirations in recurring absolute terms, such as at midnight on the first day of every month. This can be useful for a caching mechanism that requires expiration policies other than standard absolute or sliding expirations.

Appendix 3: Reviewing Performance Data

This appendix contains performance data comparing the performance of the different caching technologies discussed in Chapter 2, "Understanding Caching Technologies."

After you narrow down your choice of caching mechanism based on the scope, lifetime, and location required, you can use these performance testing results to further guide you in choosing the most appropriate cache technology for your application. To use this data, you must know the approximate size of the items you will be caching and the approximate user load that your application will need to support.

Introducing the Test Scenarios

Each technology was tested and performance monitored in two different ways:

  • Changing the size of the cached items—Firstly, the time taken to insert items into the cache and retrieve them from the cache and the number of requests that could be handled per second was monitored for items of various sizes. These results show you which caching technology is best to use for small and large cache items. When the cache storage resides in a different process than the application, the size of the cached items can affect performance. When the data is retrieved or stored it needs to be serialized, and serialization is a relatively time and resource consuming operation.

  • Increasing the number of concurrent requests—In the second test, the time taken per request and the number of requests that could be handled per second was monitored as the number of users was increased. These results show you which caching technology is best to use for different expected user loads.

    Workload can affect the performance of an application. Because each cache storage technology handles multiple requests differently (for example, using different locking techniques), the technology you choose can affect the scalability of your application.

Each of these test scenarios was carried out for both retrieving items from the cache and storing items in the cache.

Defining the Computer Configuration and Specifications

The tests were carried out in a lab using a classic three-tier architecture: a database server, a Web server, and multiple client computers. The network configuration is shown in Figure 7.3.

Ee957910.f07cac03(en-us,MSDN.10).gif

Figure 7.3. Test bed configuration

All computers were running Windows 2000 Advanced Server with Service Pack 2. The database server was using SQL Server 2000 and the Web server was using Internet Information Service 5.0. Table 7.4 shows a summary of the computer specification for each computer in the test system.

Table 7.4: Computer specifications

Computer Type and CPU Number of CPUs Memory Disk
Client Dell PowerEdge 2550, 1GHz 2 256 MB 16.9 GB
Web server Compaq ProLiant DL380, 2GHZ 2 1 GB 16.9 GB
Database server Compaq ProLiant DL380, 2GHZ 2 1 GB 16.9 GB

The following graphs results were obtained running the described tests on these computers.

Presenting the Performance Test Results

This section presents the results of the performance testing. Because all of the caching technologies were similarly tested some of the results are obvious.

All of the tests noted that the ASP.NET view state doesn't perform well; this is because the data is transported back and forth between the client and the server. Another obvious result is that the in-process technologies, such as the ASP.NET cache and static variables, provide the best performance. This is because the cache itself exists within the same process as the application so no data serialization is required.

The rest of the results shown in the following graphs vary, but you can see that SQL Server 2000 and remoting singletons perform similarly. This is because they are both out-of-process technologies that require serialization.

The only technology that behaves significantly differently is the memory mapped files. This is because the memory-mapped file technology is un-managed and is implemented by streaming data to and from specified memory locations. Memory-mapped files are neither an in-process technology nor an out-of-process technology.

Testing Cached Items of Different Sizes

This test compared the performance of various caching technologies when retrieving items from the cache and storing items in the cache using different sizes of cache items.

Retrieving Items from the Cache

The in-process technologies, such as the ASP.NET cache and static variables, produce the best performance when retrieving large items from the cache. This is expected because these technologies don't require serialization. Memory-mapped files produce acceptable results for items up to about 10 MB in size, as do the ASP.NET session, SQL Server 2000, and remoting singletons for up to about 200 KB items. View state shows the worst performance, taking about twice the time of the other technologies to retrieve even very small items.

Figure 7.4 shows the effect of the item size on the retrieval request execution time.

Ee957910.f07cac04(en-us,MSDN.10).gif

Figure 7.4. Item size versus retrieval request execution time

As the size of the items increases, the number of retrieval requests that the cache can handle per second stays relatively constant for the in-process technologies, but it decreases for the others. In this scenario, the ASP.NET session cache produces consistently better results for items less than 6 MB in size than the memory-mapped file cache. The other technologies do not perform well with anything other than very small items.

Figure 7.5 shows the effect of the item size on the number of retrieval requests serviced per second.

Ee957910.f07cac05(en-us,MSDN.10).gif

Figure 7.5. Item size versus retrieval requests per second

These figures show that ASP.NET session and static variables produce consistently good results for retrieving all sizes of items from the cache.

Storing Items in the Cache

The results for the time taken to store increasingly sized items in the cache are similar to those for retrieving items for all technologies except for SQL Server 2000, which is relatively slower. This is because when you retrieve data, SQL Server automatically stores frequently used tables in memory to minimize input/output operations and to improve performance. So although the retrieval of SQL Server items is similar to the other technologies, the storage of those items is significantly slower due to the need for SQL Server to do more than simply store the data, for example, to create indexes.

Figure 7.6 shows the effect of the item size on the storage request execution time.

Ee957910.f07cac06(en-us,MSDN.10).gif

Figure 7.6. Item size versus storage request execution time

The results for the effect of item size on the number of storage requests that can be handled per second are very similar to the results for the retrieval test, and the reasoning behind those results is the same.

Figure 7.7 shows the effect of the item size on the number of storage requests serviced per second.

Ee957910.f07cac07(en-us,MSDN.10).gif

Figure 7.7. Item size versus storage requests per second

From all of these results you can deduce that the best performing caches are based on the in-process technologies, and the worst are based on out-of-process technologies. However, the data presented here can help you make the correct choice of technology based on the size of the items you need to cache alongside all the other constraints discussed in Chapter 2, "Understanding Caching Technologies."

Testing Increasing Numbers of Concurrent Requests

This test compared the performance of various caching technologies when retrieving items from the cache and storing items in the cache under different user loads.

Retrieving Items from the Cache

This test found that the time taken to retrieve 200 KB items from the cache did not vary significantly for any of the technologies with a user load of up to about 10 users. However, beyond this, it was found that the in-process technologies (for example, the ASP.NET cache and static variables), the ASP.NET session, and memory-mapped files produced a consistently good performance, whereas SQL Server 2000 and remoting singleton did not perform so well. View state showed unacceptable results for any user load greater than about 10 users.

Figure 7.8 shows the effect of the user load on the retrieval request execution time.

Ee957910.f07cac08(en-us,MSDN.10).gif

Figure 7.8. User load versus retrieval execution time

As the number of the users increases, the number of retrieval requests that the cache handles per second grows until a bottleneck is hit and then the number of requests per second flattens out. For the in-process technologies, you can see that the amount of requests per second at the bottleneck (about 100 users) is much higher than the out-of-process ones.

Figure 7.9 shows the effect of the user load on the number of retrieval requests serviced per second.

Ee957910.f07cac09(en-us,MSDN.10).gif

Figure 7.9. User load versus retrieval requests per second

The static variable cache produces the best retrieval results for increasing user load, as it did for increasing item sizes.

Storing Items in the Cache

The results for the time taken to store items in the cache as the user load increased are similar to those for retrieving items for all technologies except for SQL Server 2000, which is relatively slower. This is because when you are storing data, SQL Server has to index the inserted data in the required tables and to physically store the data on the disk. These types of operations take a relatively significant amount of time to complete.

Figure 7.10 shows the effect of the user load on the storage request execution time.

Ee957910.f07cac10(en-us,MSDN.10).gif

Figure 7.10. User load versus storage request execution time

The results for the effect of user load on the number of storage requests that can be handled per second are very similar to the results for the retrieval test, and the reasoning behind those results is the same.

Figure 7.11 shows the effect of the user load on the number of storage requests serviced per second.

Ee957910.f07cac11(en-us,MSDN.10).gif

Figure 7.11. User load versus storage requests per second

The performance tests for differing user loads show the same overall results as the differing item size tests: that the in-process caching technologies perform significantly better than out-of-process systems. Use the data presented here along with your own parameters, estimated user load and item size data, to select the appropriate caching technology for your application within the technology constraints discussed in Chapter 2, "Understanding Caching Technologies."

patterns and practices home