Lesson 1: Designing Caching Strategies


Lesson 1: Designing Caching Strategies

image from book

Estimated lesson time: 60 minutes

image from book

Caching is the process of storing data in a cache until it is needed again. A cache is simply an in-memory representation of some data. The primary reason for caching data is to increase application performance. Caching is a good idea in cases where the data is expensive to retrieve and not likely to change. In many cases, you can achieve a significant performance advantage just by implementing the most basic caching.

One of the most common ways to use caching is in the population of large datagrid controls that need to be navigated with paging. In this case, it would be very costly to requery the database every time the user goes to the next page. Caching enables you to place the data returned from ADO.NET into a cache object. You can then use the cache object as the data source for the datagrid control.

What is Output Caching?

ASP.NET enables you to implement three types of caching. The first two, page level and user control level, are referred to as output caching; they are the easiest to implement. They involve adding a line of code to the top of the Web page or user control file. The third type of caching involves using the cache API to implement custom caching functionality.

Page-Level Output Caching

To implement page-level caching, you only need to specify the OutputCache directive at the top of the HTML for the Web page or .aspx file. This is used both for page-level caching and user control-level caching. Refer to Table 3-1 for the attributes that are used with the OutputCache directive.

Table 3-1: Attributes of the OutputCache Directive
Open table as spreadsheet

Attribute

Description

Duration

A required attribute; duration represents the time in seconds that the page or user control is cached.

Location

Only used for page-level caching; this specifies where the output cache will be located. This can be set with a value of Any, Client, Downstream, Server, None, or ServerAndClient.

CacheProfile

Only used for page-level caching; this is a name that can be assigned to the cache settings.

NoStore

Only used for page-level caching; this is a Boolean value that determines whether secondary storage can be used for sensitive information. Setting the value to true signifies that secondary storage cannot be used.

SqlDependency

Specifies the name of a database and table that the output cache depends on.

VaryByCustom

Used when doing custom caching. Setting the value to Browser indicates that that cache will be varied by browser name and version. Setting the value to a custom string means you will have to add a GetVaryByCustomString method to your Global.asax file.

VaryByHeader

Only used for page-level caching; this can contain one or more header names separated by semicolons. If multiple header names are specified, the cache will contain a different version for each header.

VaryByParam

You need to include this attribute or the VaryByControl attribute. This will specify one or more query string values that are separated by semicolons. If multiple parameters are specified, the cache will contain a different version for each parameter. Setting this value with an * indicates that all parameters will be used to vary the cache.

VaryByControl

You need to include this attribute or the VaryByParam attribute. This will specify one or more control IDs that are separated by semicolons. If multiple control IDs are specified, the cache will contain a different version for each control.

Shared

Only used for user-level caching; this is a Boolean value that determines whether the output can be shared with multiple pages.

The OutputCache directive will be placed at the top of the code file, before any other directives or HTML. The following is an example of a typical directive that would be placed in a file with an .aspx extension:

 <%@ OutputCache Duration="15" VaryByParam="*" %> 

In this example, the Duration and VaryByParam attributes were used. The Duration attribute specifies that the page will be cached for 15 seconds. The VaryByParam attribute specifies that the cache will be varied for all query string variables.

Best Practices 

Avoid using asterisks

Only use an asterisk (*) for the VaryByParam attribute if you know the content is fairly static. Otherwise, you are going to be consuming a lot of system resources, storing an exponentially large number of versions in the cache. This could cause you to get little or no performance advantage from caching.

Implementing page-level caching is a quick and easy way of gaining a performance advantage for your Web pages. However, you might have to modify the duration attribute and the VaryByParam or VaryByControl attributes to find the optimum settings for your application. You can use a utility such as the Microsoft Web Application Stress Tool, which is free and available for download at http://www.microsoft.com/downloads/details.aspx?FamilyID=&DisplayLang=en. This utility will simulate the execution of Web pages by multiple browsers, so you can evaluate the performance and scalability of your Web pages.

User Control-Level Output Caching

User control-level output caching, also known as fragment caching, is similar to page-level caching. The main difference is that the caching is restricted to the contents of the user control and not the entire page in which it is hosted. This gives you finer control over what is being cached. You place the OutputCache directive at the top of the file, giving it the .ascx extension. Refer to Table 3-1 for the list of attributes that can be used with this directive. Notice that some of the attributes are used only for page-level caching. The Shared attribute, however, is only applicable for user control-level caching.

User control-level caching can be beneficial in cases where you have the contents of an application's header and footer stored in user controls. This is a common practice; in these cases, the content in the header and footer is not likely to change, so it is a perfect candidate for caching.

In addition to using the OutputCache directive in your page, you can use the PartialCaching-Attribute class in the code for the user control. For example, the following code can be placed in the code-behind file for the user control, which enables it to be cached for 30 seconds:

 //C# [PartialCaching(30)] public partial class WebUserControl : System.Web.UI.UserControl {     //Code for the class would go here } 'VB <PartialCaching(120)> _ Partial Class WebUserControl     Inherits System.Web.UI.UserControl     'Code for the class goes here End Class 

Designing Custom Caching Functionality

In some cases, even user-control level caching does not give you the level of granular control that you need from your caching strategy. Alternatively, you can use the cache API to store specifically chosen data in a cache object. This is the most powerful method of caching available, but it also requires the most development time to implement.

The System.Web.Caching namespace contains a Cache class that can be used to add and delete items from the cache. This is only applicable to ASP.NET applications, although you can still implement caching with a Windows Forms application.

Exam Tip 

For this exam, you only need to focus on using the System.Web.Caching namespace with an ASP.NET application. You do not need to know how to implement caching with a Windows Forms application.

The first step in caching is to add an item to the cache. This is done using either the Insert or Add methods. The Insert method will add the item, but if an item with the same name already exists, it will be replaced. The Add method will not replace a duplicate; instead, it will raise an exception.

When the item is added, you will assign a key and value pair that is used to identify the item. The following code adds an item to the cache named "CacheKey"; this item is set with a value of "Cache Value":

 //C# Cache["CacheKey"] = "Cache Value"; 'VB Cache("CacheKey") = "Cache Value" 

In the previous example, the item was added directly to the cache without using the Insert or Add method. Alternatively, you could use those methods as well. An example of using the Insert method is as follows:

 //C# //Use the Insert method to add an item to the cache Cache.Insert("CacheKey", "Cache Value", null,           System.Web.Caching.Cache.NoAbsoluteExpiration,           System.Web.Caching.Cache.NoSlidingExpiration,           System.Web.Caching.CacheItemPriority.Normal,           null); 'VB 'Use the Insert method to add an item to the cache Cache.Insert("CacheKey", "Cache Value", Nothing, _           System.Web.Caching.Cache.NoAbsoluteExpiration, _           System.Web.Caching.Cache.NoSlidingExpiration, _           System.Web.Caching.CacheItemPriority.Normal, _           Nothing) 

In the previous code example, the fourth parameter was set with a value of NoAbsoluteExpiration. This indicates that the item should never expire. The NoSlidingExpiraton value indicates that the item expiration will never be reset. Finally, the CacheItemPriority parameter with a value of Normal indicates that the item will not be removed until all BelowNormal and Low priority items are removed.

The Add method uses parameters similar to the Insert method. An example of using this method is as follows:

 //C# //Use the Add method to add an item to the cache Cache.Add("CacheKey", "Cache Value", null,             DateTime.Now.AddMinutes(2),             System.Web.Caching.Cache.NoSlidingExpiration,             System.Web.Caching.CacheItemPriority.High,             null); 'VB 'Use the Add method to add an item to the cache Cache.Add("CacheKey", "Cache Value", Nothing, _             DateTime.Now.AddMinutes(2), _             System.Web.Caching.Cache.NoSlidingExpiration, _             System.Web.Caching.CacheItemPriority.High, _             Nothing) 

This code example specified an expiration date of two minutes. In addition, the priority value was set to high, which means that this is the last item that will be deleted from the cache.

To remove an item from the cache, you use the Remove method. This might be necessary when the item has no expiration date. Otherwise, an item will remain in the cache after it is no longer needed and will consume unnecessary system resources. A code example using the Remove method is as follows:

 //C# Cache.Remove("CacheKey"); 'VB Cache.Remove("CacheKey") 

In some cases, you might want to store the results of a database query in the cache. Just as with a string value, you can store a DataSet in the cache. For example, the following code will first check to see whether the DataSet has been stored in the cache. If it has not, then it will retrieve the data using a command object. Otherwise, it will just use the DataSet that resides in the cache object named dsProductsCache.

 //C#      DataSet dsProductsCache;         //Look to see if the Dataset is already in the cache         dsProductsCache = (DataSet)Cache["dsProducts"];         if (dsProductsCache == null)         {             DataSet dsProducts = new DataSet("Products");             SqlDataAdapter adapter = new SqlDataAdapter();             //Initiate the connection to SQL Server             String connString = @"server=.\SQL2005STD;" +                                    "Integrated Security=SSPI;" +                                    "Database=AdventureWorks";             SqlConnection conn = new SqlConnection(connString);             //Define the query that will be executed             String queryString = "select * from production.product";             //Define the query that will be executed             SqlCommand cmd = new SqlCommand(queryString, conn);             //Populate the adapter with results of the query             adapter.SelectCommand = cmd;             adapter.Fill(dsProducts);             dsProductsCache = (DataSet)dsProducts;         }         try         {             //Set the datasource for the dataViewGrid control on the form             grdProducts.DataSource = dsProductsCache.Tables[0];             grdProducts.DataBind();         }         catch (Exception ex)         {             //Put exception handling here         } 'VB Dim dsProductsCache As New DataSet("dsProductsCache")         dsProductsCache = Cache("dsProducts")         If dsProductsCache Is Nothing Then             Dim dsProducts As New DataSet("Products")             Dim adapter As New SqlDataAdapter()             'Initiate the connection to SQL Server             Dim connString As String = "server=.\SQL2005STD;" & _                                        "Integrated Security=SSPI;" & _                                        "Database=AdventureWorks"             Dim conn As New SqlConnection(connString)             'Define the query that will be executed             Dim queryString As String = "select * from production.product"             'Define the query that will be executed             Dim cmd As New SqlCommand(queryString, conn)             'Populate the adapter with results of the query             adapter.SelectCommand = cmd             adapter.Fill(dsProducts)             dsProductsCache = dsProducts         End If         Try             'Set the datasource for the dataViewGrid control on the form             grdProducts.DataSource = dsProductsCache.Tables(0)             grdProducts.DataBind()         Catch ex As Exception             'Put exception handling code here         End Try 

Using Query Notifications

Query notifications is a new feature available with SQL Server 2005. It is available through the SQL Native Client provider. Query notifications allows applications to be notified when certain data has changed. Therefore, applications only need to refresh DataSets when the data is different and not every time the page is requested. This is particularly useful when establishing a caching strategy because you can control precisely when the cache is refreshed based on an actual need and not just when the cache expires.

The notifications are available because SQL Servers Service Broker is continually polling the server looking for updates. When the notification is established, a time-out period is assigned, and the notification stays active until that period has elapsed. You can cancel the notification prior to the time-out period by executing a notification using the same query but with a time-out period of zero.

To establish a query notification, you must first create a queue and a service. This can be accomplished with the following Transact-SQL code:

 USE AdventureWorks CREATE QUEUE myQueue CREATE SERVICE myService ON QUEUE myQueue ([http://schemas.microsoft.com/SQL/Notifications/ PostQueryNotification]) 

At this point, you can see the queue by going to SQL Server Management Studio and expanding the Service Broker node for the AdventureWorks database. You should see a node named Queues that, when expanded, looks like Figure 3-1.

image from book
Figure 3-1: Expanding the Service Broker node and looking at the available queues for the AdventureWorks database

There is also a node for Services that enables you to see the service you just created. At this point, you instantiate a SqlNotificationRequest object to create your notification. You do so in your Visual Studio application at the point that you are ready to query the database. The first thing to do is add a reference to the System.Data.Sql namespace at the top of your code file, like so:

 //C# using System.Data.Sql; 'VB Imports System.Data.Sql 

You will also need to instantiate a SqlNotificationRequest object and set the UserData property with the value of a new Globally Unique IDentifier (GUID). You will set other properties, such as the time for the request to be active and the name of the Service Broker service, as in the following code:

 //C# //Instantiate the Notification Request object SqlNotificationRequest sqlNotify = new SqlNotificationRequest(); //Set a unique identifier for this request sqlNotify.UserData = Guid.NewGuid().ToString; //Specify the SQL Server Service Broker Name sqlNotify.Options = "myService"; //Specify a timeout of 20 minutes or 1200 seconds sqlNotify.Timeout = 1200; 'VB 'Instantiate the Notification Request object Dim sqlNotify As New SqlNotificationRequest 'Set a unique identifier for this request sqlNotify.UserData = Guid.NewGuid().ToString 'Specify the SQL Server Service Broker Name sqlNotify.Options = "myService" 'Specify a timeout of 20 minutes or 1200 seconds sqlNotify.Timeout = 1200 

The last thing to do is to associate the SqlNotificationRequest object with a query or command object. This is done by setting the Notification property for a command object, as in the following example:

 //C# //Associate the SqlRequestNotification object //with a command created earlier in the code cmd.Notification = sqlNotify; 'VB 'Associate the SqlRequestNotification object 'with a command created earlier in the code cmd.Notification = sqlNotify 

At the point the command is executed and either a DataReader or a DataSet is populated with the query results, the notification will be registered. The next step is to create a secondary thread that watches the Service Broker queue and waits for changes.

Before you can use threading, you need to set a reference at the top of your code file to the System.Threading namespace. You can then add code that creates a thread and listens for changes. In the following code, a reference is made to a method named ListenMethod, which is where the code that listens for changes resides:

 //C# // A separate listener thread is needed to // monitor the queue for notifications. Thread listener = new Thread(ListenMethod); listener.Name = "Query Notification Watcher"; listener.Start(); 'VB ' A separate listener thread is needed to ' monitor the queue for notifications. Dim listener As New Thread(AddressOf ListenMethod) listener.Name = "Query Notification Watcher" listener.Start() 

The ListenMethod will contain code that executes a query against the Service Broker queue. This will be done using an ordinary command object, and the query string for this command will appear as follows:

 WAITFOR (RECEIVE * FROM myService); 

Once the command object is executed and returned into a DataReader or a DataSet object, you can loop through the results and perform some additional processing. You can also register an OnNotificationComplete method that performs some code after the notification is complete.

Designing a Refresh Strategy

Once you have determined that you will use caching and have selected a caching method, you need to determine how you will refresh the data. In the previous section, you saw how query notifications could be used to notify an application when the data has changed. For developers not willing or able to implement query notifications, you can alternatively refresh the data on a scheduled basis or on demand. When using the Cache object, you can set an expiration using either the Add or Insert method. Add includes the same options as the Insert; however, Add returns what you've added to the cache. Also, Add will not replace an existing cache item.

The Add method accepts an expiration as either an absolute datetime value or as a timespan value. For example, the following code can be used to add an item to the cache, and that item will expire one day from the day it was created:

 //C# //Add an item to the cache that will expire in 1 day Cache.Add("CacheKey", "Cache Value", null,     null, System.TimeSpan.FromDays(1d),     CacheItemPriority.Normal, null); 'VB 'Add an item to the cache that will expire in 1 day Cache.Add("CacheKey", "Cache Value", Nothing, _      Nothing, System.TimeSpan.FromDays(1.0), _      CacheItemPriority.Normal, Nothing) 

In some cases, it might be necessary to allow users the option of manually refreshing the cache. To accomplish this, provide a button to click that removes the appropriate value from the cache. You can do so using the Remove method. At this point, the Cache object would be empty and would be refilled the next time the object was accessed. For an example of this code, see the section titled "Designing Custom Caching Functionality."

Lab: Implementing Output Caching

In this lab, you implement output caching on a page-level basis. In Exercise 1, you will see the performance effects of changing certain attributes for the OutputCache directive. You will also see the effect of eliminating caching all together.

Exercise 1: Use the OutputCache Directive

image from book

In this exercise, you will create a simple ASP.NET application that connects to a SQL Server 2005 database and populates a GridView control with the results of a SQL query.

  1. Open Microsoft Visual Studio 2005.

  2. On the File menu, select New, Project.

  3. In the New Project dialog box, expand the Other Project Types node, select Visual Studio Solutions, and then select Blank Solution.

  4. For your blank solution, type the name TK442Chapter3, and place it into a directory of your choosing.

    A new solution file is created, and you can now add multiple projects to this solution. You will add one project for each lab included in this chapter.

  5. On the File menu, select Add, New Web Site. Select ASP.NET Web Site as the template, and type http://localhost/TK442Chapter3 as the project name. Set the language by selecting Visual Basic, Visual C#, or Visual J# from the language drop-down list box. By default, Visual Studio will select the language specified when it was first configured.

  6. From the Toolbox, drag a label control onto the Default design surface. Use the following property values for this control:

    Name = lblProducts

    Text = "Products:"

  7. From the Toolbox, drag a GridView control onto the Default design surface. Use the following property value for this control:

    Name: grdProducts

  8. Right-click Default.aspx from Solution Explorer, and select View Code. At the top of the code file, add the following statement: Modify connection strings to match your environment.

     //C# using System.Data.SqlClient; using System.Data; 'VB Imports System.Data.SqlClient Imports System.Data 

  9. Add the following code to the Default class file:

     //C# protected void Page_Load(object sender, EventArgs e)     {         DataSet dsProducts = new DataSet("Products");         SqlDataAdapter adapter = new SqlDataAdapter();         //Initiate the connection to SQL Server         String connString = @"server=.\SQL2005STD;" +                                "Integrated Security=SSPI;" +                                "Database=AdventureWorks";         SqlConnection conn = new SqlConnection(connString);         //Define the query that will be executed         String queryString = "select * from production.product";         //Define the query that will be executed         SqlCommand cmd = new SqlCommand(queryString, conn);         try         {             //Populate the adapter with results of the query             adapter.SelectCommand = cmd;             adapter.Fill(dsProducts);             //Set the datasource for the dataViewGrid control on the form             grdProducts.DataSource = dsProducts.Tables[0];             grdProducts.DataBind();         }         catch (Exception ex)         {         }         finally         {             if (adapter != null)             {                 adapter.Dispose();                 adapter = null;             }             if (cmd != null)             {                 cmd.Dispose();                 cmd = null;             }             if (conn != null)             {                 if (conn.State == ConnectionState.Open)                 {                     conn.Close();                 }                     conn = null;             }         }     } 'VB Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load         Dim dsProducts As New DataSet("Products")         Dim adapter As New SqlDataAdapter()         'Initiate the connection to SQL Server         Dim connString As String = "server=.\SQL2005STD;" & _                                     "Integrated Security=SSPI;" & _                                     "Database=AdventureWorks"         Dim conn As New SqlConnection(connString)         'Define the query that will be executed         Dim queryString As String = "select * from production.product"         'Define the query that will be executed         Dim cmd As New SqlCommand(queryString, conn)         Try             'Populate the adapter with results of the query             adapter.SelectCommand = cmd             adapter.Fill(dsProducts)             'Set the datasource for the dataViewGrid control on the form             grdProducts.DataSource = dsProducts.Tables(0)             grdProducts.DataBind()         Catch ex As Exception         Finally             If Not (adapter Is Nothing) Then                 adapter.Dispose()                 adapter = Nothing             End If             If Not (cmd Is Nothing) Then                 cmd.Dispose()                 cmd = Nothing             End If             If Not (conn Is Nothing) Then                 conn.Close()                 conn = Nothing             End If         End Try     End Sub 

  10. Right-click the Default.aspx file from Solution Explorer, and select View Designer. Select the Source tab, and add the following code to the top of the HTML (above the Page directive):

     <%@ OutputCache Duration="30" VaryByParam="*" %> 

  11. On the File menu, select Save All.

  12. Click Build, click Build Web Site, and ensure that the build succeeded.

image from book

The completed code examples, in both Visual Basic and C#, are available in the \Labs\Chapter 03 folder on the companion CD.

Important 

Lab requirements

You will need to have both SQL Server and the Web Application Stress Tool installed before you can complete this lab. Refer to the Introduction for setup instructions.

Exercise 2: Evaluate the Effects of Caching

image from book

This exercise will show the effects of caching by running tests with the Microsoft Web Application Stress Tool and measuring the results in System Monitor.

  1. Open the Microsoft Web Application Stress Tool. (Refer to the Introduction for setup instructions if you have not downloaded and installed this tool yet.) Select Record from the Create New Script dialog box.

  2. Click Next to proceed to step 2 of the Browser Recorder. Click Finish from step 2 of the Browser Recorder. At this point, a browser window will open. Type the URL http://localhost/TK442Chapter3/Default.aspx.

  3. Wait for the page to render, and ensure that you see a grid listing all the products from the AdventureWorks database.

  4. Return to the Web Application Stress Tool, and click Stop Recording.

  5. Open System Monitor by clicking Start, Control Panel, Administrative Tools, Performance.

  6. Click Add (the + icon on the Toolbar), and add the following counters for the ASP.NET Applications performance object:

    • Cache Total Hits. The average for this counter should be high.

    • Cache Total Misses. The average for this counter should be low.

    • Cache Total Hit Ratio. The average for this counter should be high.

    • Cache Total Turnover Rate. This counter might spike from time to time, but the average should be high.

  7. Return to the Web Application Stress Tool, and make sure you have selected the New Recorded Script node that contains a GET for the URL entered in step 2.

  8. Click Run from the Scripts menu. The script should take about a minute to complete. While it is running, you can switch back to System Monitor and watch the values of each of the counters added in step 6.

  9. Once the script is complete, click View, and then Reports from the Web Application Stress Tool. A window should open, and you might have more than one recorded script node to select. You might have to expand all the nodes and look for the entry with the most recent time stamp. The results will vary, but you should see the number of hits and requests per second. When run on a test laptop, the following results were attained:

     Number of hits:               1897 Requests per Second:          31.60 

  10. Return to Visual Studio 2005, and open the TK442Chapter3 solution if it is not already open.

  11. Right-click the Default.aspx file from Solution Explorer, and select View Designer. Select the Source tab, and modify the OutputCache directive so it looks like this:

     <%@ OutputCache Duration="600" VaryByParam="*" %> 

  12. On the menu, click File, Save All.

  13. Return to the Web Application Stress Tool, and on the Scripts menu, select Run. The script should take about a minute to complete. While it is running, you can switch back to System Monitor and watch the values of each of the counters added in step 6.

  14. Once the script is complete, view the report by clicking View, Reports and selecting the entry with the latest time stamp value. When run on a test laptop, the following results were attained:

     Number of hits:               2733 Requests per Second:          45.54 

    Notice that the number of hits increased because you increased the duration for the cache.

  15. Return once more to Visual Studio 2005 and the Source tab for the Default.aspx file. Remove the OutputCache directive entirely.

  16. Click File and Save All.

  17. For the last time, return to the Web Application Stress Tool, and click Run from the Scripts menu. While it is running, you can switch back to System Monitor and watch the values of each of the counters added in step 6.

  18. Once the script is complete, view the report by clicking View, then Reports, and select the entry with the latest timestamp value. When run on a test laptop, the following results were attained:

     Number of hits:               455 Requests per Second:          7.58 

    Notice the significant drop in the number of hits. You can easily see the benefit of using output caching.

image from book




MCITP Self-Paced Training Kit Exam 70-442  .Designing and Optimizing Data Access by Using Microsoft SQL Server 2005
MCITP Self-Paced Training Kit Exam 70-442 .Designing and Optimizing Data Access by Using Microsoft SQL Server 2005
ISBN: 073562383X
EAN: N/A
Year: 2007
Pages: 162

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net