| One of the most important
factors in building high-performance, scalable Web applications is the
ability to store items, whether data objects, pages, or even parts of
a page in memory the initial time they are requested. You can store these
items on the Web server or on other software in the request stream, such
as a proxy server or at the browser. This allows you to avoid recreating
information that satisfied a previous request. . Known as caching, it
allows you to use a number of techniques to store page output or application
data across HTTP requests and reuse it. When the server does not have
to recreate information you save time and resources, and throughput and
scalability increase.
ASP.NET provides two types of caching that you can use to create high-performance
Web applications. The first is called output caching, which allows you
to store dynamic page and user control responses on any HTTP 1.1 cache-capable
device in the output stream, from the originating server to the requesting
browser. On subsequent requests, the page or user control code is not
executed; the cached output is used to satisfy the request. The second
type of caching is traditional application data caching, which you can
use to programmatically store arbitrary objects, such as data sets, to
server memory so that your application can save the time and resources
it takes to recreate them.
.NET Developers, particularly those of us who came from the classic ASP
space (that's almost everyone, isn' t it...) are used to storing items
like a recordset that will be used to populate a dropdown list of States
in the Application object. However we are informed by the ASP.NET gurus
that the Cache class is more efficient. The Cache class was designed for
ease of use. By using keys paired with values, you can place items in
the Cache and later retrieve them, pretty much the same way you do with
Session and Application.
While the Cache class offers a simple interface for you to customize
cache settings, it also offers powerful features that allow you to customize
how items are cached and how long they are cached. For example, when system
memory becomes scarce, the cache automatically removes seldom used or
unimportant items to allow memory to be used to process a high volume
of requests. This technique is called scavenging. It is one of the ways
that the cache ensures that data that is not current does not consume
valuable server resources.
You can instruct the Cache to give certain items priority over other
items when it performs scavenging. To indicate that a specific item is
of greater or lesser importance than another, specify one of the CacheItemPriority
enumeration values when you add an item using the Cache.Add method
or Cache.Insert method.
ASP.NET allows you to define the validity of a cached item, based on
an external file, a directory, or another cached item. These are called
file dependencies and key dependencies.
If a dependency changes, the cached item is invalidated and removed from
the Cache. You can use this technique to remove items from the Cache when
their data source changes. For example, if you write an application that
processes stock quotes from an XML file, you can insert the data from
the file in the Cache and maintain a dependency on that XML file. When
the file is updated, perhaps even by a separate application, the item
is removed from the cache, your application rereads the file, and a new
version of the item is inserted.
ASP.NET 1.1. Offers Cache Improvements
One of the irritating deficiencies of ASP.NET 1.0 for partial page-level
or "fragment caching" usage is the fact that user control output
caching is cached on a per - page basis. If multiple pages reference the
same user control, the server must maintain separate cached instances
of the control for each of the pages. In ASP.NET 1.1, this is corrected
with the new Shared attribute. This is a boolean value that controls whether
cached control output can be shared among instances. For example, if you
had an ascx userControl that had a child DropDownList control "ddList1"
with a list of US states in it, you would create shared caching with the
directive:
<%@ outputCache Duration="120" VaryByControl="ddList1"
Shared="True" %>
This article is not intended to be a tutorial on the Cache class - there
is plenty of information on this in the .NET Framework SDK and at other
resources you can find. What I am focusing on here is whether in fact
the Cache class is more efficient than Application, and that by making
a paradigm shift and learning to store items such as DataSets in Cache
instead of Application, you may see a rather dramatic improvement in scalability
and throughput.
To Illustrate this, I put together a small ASP.NET application that sports
two pages: a DataSetCache page that checks the Cache, fills it with a
Customers and an Employees DataSet from Northwind, and binds two page-level
DataGrids, and a DataSetApplication page that does exactly the same, but
employing the Application object to do the caching. I also created a third
page that uses no caching, creating and binding each DataSet on each page
load.
The number of lines of code in each page is virtually identical, the
database code is identical. The only difference is that we are using the
Cache class to store our DataSet on one page and the Application class
to store it in the other.
My page-level code (shown here is the Cache class example) looks like
the following:
private void Page_Load(object sender, System.EventArgs
e)
{
DataSet ds=(DataSet)HttpContext.Current.Cache["dataset"];
if(ds==null)
{
HttpContext.Current.Cache["dataset"]=
DAL.DataUtil.GetDataSet("server=peterlap;DataBase=Northwind;user
id=sa;","Select * from cUstomers");
}
ds=(DataSet)HttpContext.Current.Cache["dataset"];
DataGrid1.DataSource=ds.Tables[0];
DataGrid1.DataBind();
DataSet ds2=(DataSet)HttpContext.Current.Cache["dataset2"];
if(ds2==null)
{
HttpContext.Current.Cache["dataset2"]=
DAL.DataUtil.GetDataSet("server=peterlap;DataBase=Northwind;user
id=sa;","Select * from employees");
}
ds2=(DataSet)HttpContext.Current.Cache["dataset2"];
DataGrid2.DataSource=ds.Tables[0];
DataGrid2.DataBind();
}
|
My code in the helper class "DAL" (Data Access Layer) looks
like this:
public static DataSet GetDataSet(string strConnString,
string strSQL)
{
SqlConnection cn = new SqlConnection(strConnString);
cn.Open();
SqlCommand cmd =new SqlCommand(strSQL, cn);
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
da.Fill(ds);
cn.Close();
return ds;
} |
Now we fire up HOMER (Web
Application Stress tool) and set up a few tests. You can also use
Application Center Test, which comes with Visual Studio.NET version 1.0
and 1.1 (Everett). It has a much nicer graphical interface and you can
watch a chart in real time while the test is running. However, I trust
Homer better, and Homer will happily pummel your app with 200 simultaneous
threads from as many coordinated client machines as you want. App Center
test simply wasn't designed to create that type of punishment, and it
cannot be made to support multiple simultaneous test clients.
The test shown was conducted with 150 threads and no delays, for a period
of exactly one minute, after a brief warmup period to allow page objects
to be instantiated and datasets cached. Several test suites were run,
ranging fro a low of 20 threads to a high of 250 threads. The Sql Server
database for the tests resided on a separate machine from the webserver
machine, most closely simulating an actual production scenario and database
- call latency. The results below were chosen as being the most likely
middle ground reflection of expected results.
Test Results:
| |
Application
Object |
Cache
Object |
No
Caching |
| Total Requests |
130 |
138 |
65 |
| Avg. Requests/Sec. |
2.17 |
2.30 |
1.08 |
As can be seen, without any caching ("No Caching, far right column)
we get the poorest results. Additionally, and not shown in the chart above,
is the fact that out of the 65 total completed requests with no caching,
20 of them resulted in a "503" (Service temporarily overloaded)
response code. Both Application and Cache returned 100% successful requests
in this test.
Most interesting I think is the fact that the Cache class at 138 completed
requests (vs. 130 for the Application Object) only marginally outperformed
the Application object in terms of better throughput. So I think that
when the experts claim that the Cache class is "more efficient"
than Application for storing and returning frequently - requested data
object, they may only be partially correct. One big advantage Cache does
have over Application is that it handles simultanous updating internally
without the need to use the locking mechanism.
Conclusion:
It is possible to obtain significant performance improvements in ASP.NET
applications by caching frequently requested objects and data in either
the Application or Cache classes. While the Cache class certainly offers
far more flexibility and control, it only appears to offer a marginal
advantage in terms of increased throughput over the Application class
for caching. It would be very difficult to develop a testing scheme that
could accurately measure the potential advantages of the Cache class's
built - in management of lesser-used objects through the scavenging process
as opposed to the fact that Application does not offer this feature. The
decision the developer makes in this case should be based more on the
needs and convenience of the project and its expected usage patterns.
| |
| Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform. Pete's samples at GotDotNet.com have been downloaded over 41,000 times. You can read Peter's UnBlog Here. --><-- NOTE: Post QUESTIONS on FORUMS! |  |
Do you have a question or comment about this article? Have a programming problem you need to solve? Post it at eggheadcafe.com forums and receive immediate email notification of responses.
|