Powered by Blogger.

Wednesday, August 18, 2010

Speed Up Your Site! 8 ASP.NET Performance Tips (part 4)

How can I improve the speed of my site?
Most ASP.NET web servers perform a lot of unnecessarily repetitive work.
For example, suppose you have a page with a DataGrid bound to a table called Products. Every time a user requests the Products page, ASP.NET has to:
  1. Look up the products data in the database.

  2. Process the page.

  3. Databind the product data.

  4. Render the results to HTML.

  5. Output the results to the browser.
If we assume the products list changes infrequently in comparison to how often it is requested by a user, most of that work is unnecessary. Instead of doing all that work to send the same HTML to all users, why not just store the HTML and reuse it until the Products table changes?
Solution
The ASP.NET cache provides the key to efficient storage and reuse of our HTML.
There are several ways to use the cache; we'll focus on the easiest tricks to get you started, then point you toward some resources that will help you tackle more advanced ways to utilize the cache.
The simplest solution is to use the OutputCache directive on your pages or user controls. For example, the following page will be cached for one hour (3600 seconds). You can refresh the page all you like, but the value of DateTime. Now won't change until the page is cleared from the cache. Here's the code that retrieves the current time:
Example 15.7. OutputCacheSimple.aspx (excerpt)   
  
<%@ Page Language="C#" AutoEventWireup="true"     
   CodeBehind="OutputCacheSimple.aspx.cs"   
   Inherits="chapter_15_performance.Performance.OutputCacheSimple"   
%>   
<%@ OutputCache Duration="3600" VaryByParam="none" %>   
<html xmlns="http://www.w3.org/1999/xhtml">   
<head id="Head1" runat="server">   
 <title>Output Cache Example</title>   
</head>   
<body>   
 <form id="form1" runat="server">   
   <h1>Output Cache Example</h1>   
   <div>   
     <%= DateTime.Now.ToLongTimeString() %>   
   </div>   
 </form>   
</body>   
</html>
Figure 15.5 represents our page at 11:01:41 p.m.
Figure 15.5. Loading a cached page for the first time
Figure 15.6 shows what it looks like at 11:23:01 p.m.
Figure 15.6. Subsequent reloads of a cached page showing no changes to the page content
Notice that the time didn't change, as the page wasn't re-rendered.
Now let's look at a slightly more complex caching example:
Example 15.8. OutputCache.aspx (excerpt)   
  
<%@ Page Language="C#" AutoEventWireup="true"   
   CodeBehind="OutputCache.aspx.cs"   
   Inherits="chapter_15_performance.Performance.OutputCache" %>   
<%@ OutputCache Duration="30" VaryByParam="none" %>   
<html xmlns="http://www.w3.org/1999/xhtml" >   
<head id="Head1" runat="server">   
 <title>Output Cache Example</title>   
 <script type="text/javascript">   
   var d = new Date();   
 </script>   
</head>   
<body>   
 <form id="form1" runat="server">   
 <!-- Pausing 5 seconds to simulate a database hit -->   
 <% System.Threading.Thread.Sleep(5000); %>   
 <h1>Output Cache Example</h1>   
 <div>   
   Time written by ASP.NET:   
   <%= DateTime.Now.ToLongTimeString() %>   
 </div>   
 <div>   
   Time written by Javascript:   
   <script type="text/javascript">   
     document.write(d.toLocaleTimeString())   
   </script>   
 </div>   
 <div id="divCached" style="margin-top:25px;width=300px;">   
   <script type="text/javascript">   
     var aspTime = new Date();   
     aspTime.setSeconds(<%= DateTime.Now.Second %>);   
        
     // If there are more than two seconds difference   
     // between the ASP.NET render and the javascript   
     // evaluation, the page is almost certainly cached.   
     if(Math.abs(d - aspTime) > 2000)   
     {   
       document.write('Probably Cached');   
       document.getElementById("divCached").style.backgroundColor =   
         "Coral";   
     }   
     else   
     {   
       document.write('Not Cached');   
       document.getElementById("divCached").style.backgroundColor =   
         "Aqua";   
     }   
   </script>   
 </div>   
</form>   
</body>   
</html>
The above page writes out the current time according to both ASP.NET (server side, cached) and JavaScript (client side, not cached). Additionally, if the JavaScript time is more than two seconds after the ASP.NET time, the page will report that it has probably been cached.
The first time this page is viewed, there's a five-second delay (due to the call to Thread.Sleep), then the page shown in Figure 15.7 is displayed.
Figure 15.7. The page output on first load
But if you refresh the page within 30 seconds, you'll notice two differences. First, the page will return immediately. Second, it looks like Figure 15.8.
Figure 15.8. The page output 30 seconds after its initial load
Using VaryByParam to Cache Parameterized Pages
When you start to consider which pages in your application could be cached, you'll probably discover that many of them contain content that's 90% static. The remaining 10% of these pages is likely to contain one or two tiny portions that vary frequently. For example, consider a page in the example Northwind database that displays catalog information, including products filtered by category. The category is set by a parameter in the query string, so the following two URLs will yield different results:

http://www.contoso.com/Northwind/Products.aspx?Category=Seafood
http://www.contoso.com/Northwind/Products.aspx?Category=Produce

The novice programmer might apply the following code to cache these pages:
<%@ OutputCache Duration="30" %>
However, if you were to apply this code you'd quickly discover a problem -- the category filter would stop working. The first version of the page that was displayed would be cached, so the filtering logic would be ignored on subsequent page views.
That's the exact function for which the VaryByParam attribute is used. In this case, we'd change the OutputCache directive to the following:
<%@ OutputCache Duration="30" VaryByParam="Category" %>
The VaryByParam attribute tells the cache system to store a different version of the page every time it sees a new value for Category in the URL. Once this attribute is in place, our users will be able to access a cached copy of the page for both the Seafood and Produce categories.
Keep in mind that storing different versions of the page for each parameter value isn't always the best idea -- in cases where you have many different parameter values, you can quickly use a large amount of memory to hold all possible variations of the page. Use VaryByParam when your pages utilizes a limited number of parameter values.
Note that the number of seconds displayed in the JavaScript-generated time has updated to 59 seconds, while the ASP.NET time still shows 39. What this discrepancy suggests is that the server is sending the same HTML to the client for each request. Suppose that instead of just displaying the current time, we'd bound several GridViews to the results of several expensive database queries. With caching applied to such a page, the savings would be considerable.
You don't have to cache a page for long to see significant performance increases -- a cache of one minute or less can improve your site's performance dramatically. For example, adding a cache to a simple page that just binds a GridView to the Products table in the example Northwind database will increase the performance of that page by about 500%, and for a page that performs a complex database query or processor-intensive calculation, this saving is likely to be amplified even further.
Using Post-cache Substitution
While the OutputCache directive allows you to specify that the cache should vary with a specific parameter, it's not really efficient to cache a multitude of copies of one page that are nearly all identical.

Post-cache substitution, using the Substitution control, allows you to inject dynamic content into a cached page. The result is that you can cache some pages that you probably thought were unable to be cached. Read more on this subject in Scott Guthrie's article on the topic.
One of the problems you might notice with this approach, however, is latency -- the time delay between the point at which your data is updated and the moment when that same updated data reaches a user's browser. In the examples we've looked at in this solution, we displayed the same timestamp for 30 seconds after the page was rendered -- this is great for performance, but might be a problem if your users require the data to always be up to date.
Fortunately, ASP.NET 2.0 has a solution to this problem, which we'll look at next.
How do I refresh my cache when the data changes?
As we saw in the previous tip, judicious use of output caching can provide dramatic improvements in your site's performance. Output caching works by saving the generated HTML for a rendered ASP.NET page. As long as the cache is valid, future requests for that page will just return that stored HTML, rather than processing the page again, which means no page parsing, hits to the database, data binding -- any of those tasks that require precious bandwidth or processor cycles. Used correctly, caching can improve your requests-per-second on a high-traffic page by a factor of 100 or more.
For example, suppose you had a page that contained a DataGrid bound to a table called Products. Without caching, every page request would require ASP.NET to look up the product data in the database, process the page, data bind the product data, render the results to HTML, and output the results to the browser.
If the page was cached, only the first request for this page would require all that work; after that, ASP.NET would just skip all the hard work and serve up the HTML until the cache expired. The end result would be the same whether or not a cache was used: the same HTML would be sent to the browser. However, since a lot less work and network traffic is required to display cached HTML, the server can serve the cached page much faster and to more people.
One problem with cached pages is that they don't pick up data that has changed right away. To continue with the example above: if we were to add a new product to the Products table, it wouldn't show up on the page until the cache expired. Fortunately, ASP.NET 2.0 provides a good mechanism to refresh your cache automatically when the underlying data changes.
Solution
The SQL Cache Dependency was created to solve this problem. A few steps are required to set it up, but once it's up and running, you can use it throughout your whole application.
Using SQL Cache Dependencies with Older Versions of SQL Server
In this book I'll only cover the steps for configuring SQL Server Cache Dependencies on SQL Server 2005; there's a wealth of information on MSDN about how to set it up on SQL Server 2000:

First, you'll need to make sure you've enabled SQL Server Service Broker for your database. You can confirm that with the following query:
ALTER DATABASE Northwind SET ENABLE_BROKER;
Next, you'll need to start the SQL Dependency event listener. The recommended place to make the change that triggers this listener is in the Application_Start method of Global.asax:
Example 15.9. Global.asax (excerpt)  
 
void Application_Start(object sender, EventArgs e)  
{  
 string northwindConnection = WebConfigurationManager.ConnectionStr  
➥ings["NorthwindConnectionString1"].ConnectionString;  
 SqlDependency.Start(northwindConnection);  
}
Once the above code has been added to the Global.asax file, our connection can employ an SQL Cache Dependency.
To illustrate how an SQL Dependency is utilized, let's look at an example -- a simple GridView that's bound to a table. We'll drag the good old Northwind Products table onto a new page, then set the following two attributes in the SqlDataSource control:
EnableCaching = "True" SqlCacheDependency =  
   "NorthwindConnectionString1:Products"
With this simple line of code in place, the GridView won't read the Products table again until it changes. As soon as the Products table changes, though, SQL Server will notify ASP.NET to dump the cache, and the subsequent page request will reload the page from the database. The end result is that our application gains all of the performance benefits that come with caching, but our users will never see stale data.

No comments:

Post a Comment

  ©Template by Dicas Blogger.

TOPO