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:
- Look up the products data in the database.
- Process the page.
- Databind the product data.
- Render the results to HTML.
- Output the results to the browser.
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.6 shows what it looks like at 11:23:01 p.m.
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.
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.
Using
VaryByParam
to Cache Parameterized PagesWhen 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
GridView
s 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:
- ASP.NET SQL Server Registration Tool (Aspnet_regsql.exe)
- Caching in ASP.NET with the SqlCacheDependency Class
- Walkthrough: Using ASP.NET Output Caching with SQL Server
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