Friday, June 08, 2007

Tech Ed Day 5 - Scalable ASP.NET Web Applications

I only have chance to attend one session here on the final day in Orlando. Luckily it proved highly valuable, and contained plenty of information.

In Building Highly Scalable ASP.NET Web Sites by Exploiting Asynchronous Programming Models, Jeff Prosise from Wintellect explained how to make your pages work under heavy loads.

Under the hood ASP.NET has two thread pools, one for handling I/O, and the other for worker threads. The worker threads are what ASP.NET assigns a request to, and is where all the work is done in the application. There is a set number of these threads, and once they are all in use requests get placed in an application request queue. It's here where you start seeing a performance hit, as your user's requests are getting blocked as there are no threads to take the request.

Once this queue becomes too full the server starts responding to inbound requests with service unavailable errors. The key to building scalable apps is figuring out where your bottlenecks are--what is stopping that worker thread completing and returning to the pool?

If the bottleneck is CPU usage, then the only solution is to throw more servers in the web farm. But more often than not, the bottleneck is I/O operations, like file system or database access.

There are three places where we can place code that can be converted to async operations to better scale an application: Pages, HttpHandlers, and HttpModules. If you have I/O operations happening in any of these stages then they are candidates for async operations.

An asynchronous page starts its processing on one thread, and finishes it on another. ASP.NET 2.0 contains a framework to allow you to do this easily. Specify an Async property in the @Page directive in your .aspx code, and call RegisterAsyncTask in the Page_Load event.

RegisterAsyncTask takes a PageAsyncTask object, which contains event handlers for the Begin, End and Timeout events that an async operation can have. Within your Begin handler kick off an asynchronous operation and you're done.

I don't have enough time to go into detail on the HttpHandlers or HttpModules (I'm rushing off in a minute to catch a plane), but they're pretty much the same. We kick off an asynchronous operation from a Begin method, and then complete the call in an End method--standard async pattern stuff.

This is about scalability, not performance--you may see a performance increase if your tasks are capable of running in parallel, but not otherwise

So how do you call an async operation from the Begin method? If your page just goes straight to a database (i.e. instantiates a SqlConnection directly), then there is a method called BeginExecuteReader on a SqlCommand that will do this for you.

However, I'm sure that most developers will have a layer in between the page and the SqlConnection, and in these cases you do need to do some more work. You'll need to expose the Begin and End functionality of the SqlCommand up to this layer so it can be called. Depending on what is separating these layers will of course dictate how easy or hard this is to do.

In an attempt to build scalable web apps in ASP.NET, some people will try to use threading to help them out. There are 3 wrong ways to do this, and 1 right way:

DO NOT

  • Use Thread.Start - it is an unconstrained use of threads that the architecture has no control over
  • Use ThreadPool.QueueUserWorkItem - it steals threads from the ASP.NET worker thread pool
  • Use Asynchronous delegates, for the same reason as above
ONLY use threads in a custom thread pool. There is sample code for a high performance thread pool available on the Wintellect website, unfortunately I don't have time to look to give a direct link, but I'm told there is one there.

And I guess that is it for my first Tech Ed. I've mostly enjoyed my time here, and it has certainly been a very valuable experience. There's a lot of new stuff here to mull over in the coming weeks, I look forward to checking some of it out in detail.

No comments: