ASP.NET Caching and Performance

Listen to the Show

Listen to the Show!

Steve Smith, owner of ASP Alliance and Lake Quincy Media joins us today to teach us about some hidden gems in ASP.NET caching and performance. Steve’s expertise in this area comes from first-hand experience as Lake Quincy’s ad system serves over 60 requests per second and handles over 150 million requests per month. Steve is an ASP Insider, Regional Director, Microsoft MVP, INETA Speaker and fellow book author!


What performance metrics are worth tracking?

  • Requests per second
  • Time to last byte: How long it takes the user to see the page load

Indicators of performance problems include high requests per second, but very long page load times. Alternatively you may notice quick load times, but only for a small number of users. The balance of these two metrics will help you even out the performance of your website.

How do you find bottlenecks?

  • Recognize there is only one bottleneck at a time: The rule of thumb is that there is always one bottleneck – and changing anything else before addressing the bottleneck will not improve performance. Most bottlenecks in web applications are external resources, like a database, web service or the file system.

  • Avoid optimizing too early. Beware of pre-mature optimization as it may cause performance problems. Without the benefit of production environment (large number of users going to your site) you may make implementation mistakes.

    Image showing an application bottleneck

  • Measure: Run load tests and profilers to establish benchmarks

Caching Basics

Output Caching

ASP.NET included output caching as a feature from the beginning. Output caching is easy to use and has many different features available.

One feature that many people don’t know about is the control’s ability to vary by browser. This is important because if you have markup optimized for a particular browser and then a user views your site with an un-optimized browser version, your site may look broken. Using the output cache’s vary by browser parameter can help you avoid this problem.

Do you have to be worried about your output cache getting to big?

Yes, but most servers can handle the load that caching will create on the server. Caching saves the rendered markup in memory and then serves it while the cache is still valid, so many cache entries will only be around 5 to 10KB of markup.

ASP.NET’s caching also automatically manages memory so the cache will purge when the server detects memory pressure. Caching uses a least-recently-used (LRU) algorithm to determine which cache entries to purge first. The worst case scenario is that your server will begin to "thrash" alternately caching pages and purging pages upon every request.

Substitution Control

The substitution control gives you a way to display dynamic content on a cached page. The control uses a callback to generate a string to substitute in its place before serving the content to the client. For example if you wanted to display the login information for a user on the screen on a cached page, the substitution control can display the user’s name in the middle of cached content.

Even though the control requires a string, you are not limited to simple strings. You may still use rich UI controls - you would simply need to render the control’s HTML and pass it up to the substitution control.

For an example of how to tell a control to render its HTML, Steve has an example that even comes with a nice extension method that makes getting the markup easy for any control.

The only seeming drawback to using the substitution control is the lack of kernel-level caching support in IIS7.

State Bag Access Pattern Gotchas

Many developers will implement caching by using the state access pattern. While this approach is acceptable there is a gotcha you must be aware of: only make a call to the cache only ONCE during a request. If you make multiple calls to the cache an item may be expired or removed between the first and subsequent calls.

Naïve State Bag Access Pattern

public List List()
{
List myList;
if(Cache["customers"] == null)
{
myList = DAL.ListCustomers();
Cache.Insert("customers", mList, null,
DateTime.Now.AddHours(1), TimeSpan.Zero);
}
return (List)Cache["customers"];
}

Correct State Bag Access Pattern

public List List()
{
string cacheKey = "customers";
List myList =
Cache[cacheKey] as List;
if(myList == null)
{
myList = DAL.ListCustomers();
Cache.Insert(cacheKey, mList, null,
SiteConfig.CacheDuration, TimeSpan.Zero);
}
return myList;
}

Cache API

The caching API lives in the System.Web namespace, but is not limited to a web application or IIS.

Benefits of using the API include:

  • Manages memory itself
  • Supports dependencies based on
    • key
    • time
    • file system
    • databases

Limitations of ASP.NET Caching

The built-in ASP.NET caching is app-domain specific, so caching on a web farm is problematic. If you put something into the cache on server "A" the same cache does not exist on server "B". A project in its infancy from Microsoft, codenamed Velocity, will help address this issue. Products available today that supports distributed cache and session are ScaleOut State Server or Alachisoft NCache.

Write Caching

Write caching is the practice of filling a buffer of commands and sending them to the server in infrequent intervals. A Common practice for using write caching is in logging scenarios. The implementation may include a short-lived session or cache object which is accessed by a separate process powered by a timer which takes whatever has built up in the buffer and then executes the commands against the server.

For a code examples read Steve’s article, Use Write Caching to Optimize High Volume Data Driven Applications. While Steve’s article is relevant for SQL 2000 environments, Jason Follas posed an update to the write caching technique that is optimized for SQL 2005 called, Coding in SQL Server: An Evolution. Jason’s article shows you how to implement write caching which requires less code and performs even faster than Steve’s SQL 2000 example.

Finally, you could go even further and make these write caching reliable by implementing a queuing solution.

Client-Side Caching

Client-side caching includes:

  • Storing data at the browser and then manipulating it with JavaScript
  • Configuring client cache headers will store static resources at the client and proxy servers

Side Effects of Caching: Logging

Web servers log traffic statistics by counting how many times a file is served off the file system. When you implement caching pages are served from memory and not off the server’s file system. Knowing this, you may need to alter your approach to reporting the traffic on your site.

Most of the caching techniques discussed here include storing the rendered HTML of a page or a control, so request to images are still served as normal even on a cached page. Therefore as you begin to make use caching, you may want to include web beacons (or the infamous clear pixel image) to help you track statistics for your site.

Further, logging implemented with custom logic is also affected. Cached pages do not exist as control objects and therefore cannot execute the code-behind while in cache. If you have logging setup on a page the logic will only run the first time and when the cache expires.

Debugging Cached Applications

Debugging cached applications can be hard if you are expecting code to execute that never runs. A few ways you can help debugging are to:

  • Make it easy to turn off caching: Turning off caching in a development environment will allow you to go straight to problem areas

  • An object's rendered HTML is what is cached: Be aware that cached controls exist as the rendered HTML string in the cache and not the original object. If you try to run a method of a cached user control you will encounter an exception

Tips and Tricks

Note: Browsers only make two simultaneous requests per domain. By simply creating extra CNAMEs for images, style sheets and JavaScript files, you can greatly improve the load speed of a page.

Resources

kick it on DotNetKicks.com

7 Comments

  • Performance is a deep subject, it also goes into the performance (or responsiveness) your users will experience when visiting your site. I am using a pretty nice filter (actually technically it's a Page) which sets a Far Future expires date on all my static content (.css, images and so on)

    Code is LGPL btw for those wanting to experiment...

    (Download at http://ra-ajax.org)

  • I would have expected some insight on clustered caching - see velocity (from MS) and memcached (from the OS community). Do you think it's not worth worth it?

  • I totally agree that intelligent usage of caching and aggressive optimization can help very much! We currently make extensive use of ISA server for caching things like non dynamic images and pages on our various sites. Also, since upgrading to Windows Server 2008/IIS7, kernel/user cache & compression is helping alot. This combined with some custom ajax & compressed js/css, let's us squeeze out ~1500 requests/sec with plenty of room for more, spread out across 4 front end server machines.

    btw: ie8 beta 2 is really really fast! At least 2 times as fast as firefox 2.016 with our ajaxified chat ..way to go MS!

  • Thx, good review. Cache is a best way to build rich apps.

  • Hi, really nice, but i am interested in different ways to improve performance (except cashing) is there something else i can do? Thanks

  • Great Post Craig!

    Especially the part about limitations of asp.net cache. I am authoring a similar blog that explains the various bottlenecks and their remedies and agree with you there.

    Caching does improve the performance of apps no matter what anyone says. It has to be implemented in the right manner though.

  • Really nice article.
    Caching does improve the performance of apps no matter what anyone says. It has to be implemented in the right manner though.

Comments have been disabled for this content.