Solutions for Optimizing ASP.NET Applications

One of the reasons that ASP.NET has become popular as a framework for Web developers is the availability of third-party controls and tools. This third-party product support means that, when you're developing features and functionality in an ASP.NET application, you have "buy versus build" options in many areas. Usually, if the options are buying a third-party control or rolling up your sleeves and manually coding the solution yourself, the choice is an easy one. With the latter, you're looking at investing an unknown amount of time and resources into re-creating something that already exists. With the former, you get a known result and a drop-and-go solution.

What you may not realize, however, is that the possibilities for third-party "buy" options extend far beyond controls. Today, ASP.NET developers can take the same approach to many application performance optimizations. Current product-based solutions for enhancing performance and scalability - including third-party software, hardware, and services - can extend the same kinds of benefits and offer the same core value as third-party solutions provide in other areas of development. If you're looking to both improve the performance of your application and reduce your development costs and timelines, they merit serious consideration.

A Growing Need for Scalability and Performance
Most development shops are geared toward two overarching goals: delivering the desired functionality and features, and getting the application out the door as quickly as possible. In this kind of environment, performance and scalability considerations often take a back seat. But if the application actually achieves the desired result - if the site becomes incredibly popular - these considerations become a major concern.

Even if you've created the greatest application in the world, if it crashes under load or takes two minutes to load a page, nobody's going to use it. In fact, the very productivity-enhancing tools that programmers use to speed development (high-level programming languages, object-oriented techniques, etc.) often become the biggest culprits in poor performance and lack of scalability of the application. That's why the most demanding projects, such as Amazon.com, still rely primarily on highly tuned proprietary code, even though creating it requires enormous additional effort.

Microsoft designed ASP.NET to be an intelligent, flexible development framework, and it supports a variety of options for boosting application performance and scalability. Various caching techniques, page-size controls, and load-balancing optimizations, for example, can all deliver performance and scalability improvements. The problem is that these techniques require additional knowledge, time, and resources to implement. On top of these added demands, they create an unfortunate Catch 22 for developers: By using these techniques to improve performance, you're simultaneously impairing your ability to develop and maintain the application in the future, because you now have to employ more complex, highly optimized techniques for everything you do.

No one likes having to weigh these kinds of trade-offs. What many developers don't realize, however, is that they don't actually have to. For many ASP.NET optimizations, third-party solutions now exist that can provide the same or similar enhancements as manually tuned code, without the manual tuning. Product-based optimizations can't solve everything, but they can offer some significant advantages for many common optimizations. Let's look at some common optimizations and compare the build-and-buy options for addressing them.

Code-Based "Build" Optimizations
When you need to optimize application performance and scalability, the first strategies to consider are caching, minimizing page size, and distributing resources among more Web servers. ASP.NET offers options for addressing all of these areas, but while code-based approaches can offer viable solutions, they also create new challenges.

Caching
Caching offers one of the most common and effective means of enhancing performance. After all, it's easy to see how an application that requires servers to dynamically generate a new page for every request received - despite the fact that most of the content in the page is static and redundant - can waste a huge amount of resources. The main caching strategies to consider include output caching (storing and reusing static content in server memory), browser caching (storing static content in the user's browser), and data caching (storing data in the server's memory for use by any code in the application).

ASP.NET offers rich features for implementing caching, and you can use these features to create flexible caching mechanisms that conform to configurable rules and expiration policies, and are triggered by the specific data in the request for the page. In principle, these strategies reduce the amount of work required by the server to process each request, increasing the number of requests each server can handle in a given period of time. Unfortunately, manually implementing any of these caching strategies has drawbacks as well.

Coding browser caching or output caching mechanisms is complex and time-consuming, since you have to incorporate caching instructions (such as declarative directives) into all pages and controls that use them. Beyond coding the caching mechanisms, practical time and cost constraints dictate that you can't cache everything. So you have to invest time and resources into identifying those areas of the application where caching will provide the greatest value.

The biggest challenge with code-based caching strategies, however, is maintaining cache currency. For caching to work correctly, you have to develop dynamic mechanisms to recognize when the cache is out of date and respond appropriately. ASP.NET 2.0 does offer features to help simplify this process. (Cache expirations can occur automatically when the requesting server recognizes changes to a query result.) But again, actually implementing these mechanisms is not a trivial task.

Minimizing Page Size
One of the biggest culprits for poor application performance - and one that programmers often don't account for in the development process - is the size of pages. Even when you're caching static resources like JavaScript files and images on the browser, just the HTML content of a page can become significant. (I knew one developer who built and tested an entire site internally, only to find on the day it went live that each page was 1.5MB.) To clamp down on bloated page sizes, your first target should be ViewState data.

ViewState, a block of data in a hidden form control on the page, plays an important role in almost all ASP.NET applications by storing data vital to the controls on the page. However, depending on the number and type of controls used, ViewState data can get extremely large. Theoretically, you could just disable ViewState, but unfortunately many controls can't function properly without it. Your options are either to examine closely every single page for opportunities to limit ViewState without affecting functionality, or find ways to code around it.

ASP.NET 2.0 introduced one option for addressing this problem with ControlState, a new form of hidden control storage that controls can use even when ViewState is disabled. However, this approach depends on controls that actually take advantage of ControlState, and most still only use ViewState. Of course, all of these solutions increase your coding requirements as well.

Another technique you can employ to reduce the performance hit from large pages is AJAX-based controls that can refresh sections of a page without requiring a full postback of the page. AJAX has its own shortcomings, however, most notably, the complex, extensive programming required to make it work well. You can use new Microsoft-created controls and JavaScript frameworks aimed at reducing the coding requirements for AJAX, or use runtime optimizations such as Silverlight to improve the perceived performance of the application. As Web-based software techniques, however, all of these strategies put an additional burden on your network and servers.

Distribution
If you're fortunate enough to create an application that is wildly successful (if you wake up one morning and your Web servers are getting bombarded by hundreds of requests per second), and you haven't planned for that possibility from early on in your development process, you can expect some serious scalability issues. Cached objects, session state, and transient .NET objects that were not an issue under a relatively small load can rapidly accumulate to the point where they overload the memory in your servers.

The traditional solution for scaling an application has been to simply throw more hardware at the problem to distribute the load among more Web servers. The problem with this strategy is that unless you've architected your application from the start to support load-balanced, parallel processing of requests, it doesn't really work. Resource affinities in the code - hidden dependencies that require code to run on a particular thread, CPU, component instance, or server - can lock processes into a specific resource and impede effective load balancing. Ultimately, you can double the Web servers in your server farm only to find that you now have twice as many servers overloaded, and very few additional users getting data.

The most common resource affinity problem is in-process session state, which assumes that all requests from a single user session will be processed on the same Web server. To deal with the session state problem, you can take session management out of process - distributing it to a separate shared resource that all Web servers in the farm can access. ASP.NET offers built-in support for storing session state in a separate database or designated state server, but neither strategy provides an ideal solution.

In principle, embedded ASP.NET session tools should allow you to move session data out-of-process without any additional application coding. However, all of your .NET objects must be serializable to do this. Since custom classes aren't serializable by default, you have to mark your classes as serializable and make sure that all data types used in your classes are, in fact, serializable. If they aren't, you have to do some significant recoding. Once you complete this process, ASP.NET allows you to use either State Server (a free ASP.NET software package for implementing remote session storage) or SQL Server to manage session out-of-process. Unfortunately, the combination of serialization, out-of-process communication, and added network trips required by either of these solutions (reading from the database at the beginning of each session and writing to the database at the end) can make your out-of-process session take as much as six times longer as an in-process session. Effectively, you're adding scalability at the cost of performance.

Product-Based "Buy" Optimizations
Now, let's consider some third-party "buy" options to optimize the scalability and performance of your ASP.NET application. Of course, no third-party solution can compensate for poor coding or architecture, or can completely remove the need to pay attention to performance when coding. But a variety of third-party hardware, software, and service-based solutions now available can help you make some significant optimizations without touching your application code. Product-based solutions for load balancing, payload reduction, caching, and application acceleration can tackle some of the biggest performance hogs quickly and predictably.

Load Balancing
Any distribution solution requires some sort of load balancing - the ability to split the incoming workload across multiple Web servers. Microsoft provides Network Load Balancing (NLB) as part of all versions of Windows 2003 Server. NLB is an algorithmically based load-balancing solution, typically balancing workloads symmetrically across multiple servers. If you have four servers, each would take 25% of the load. You can alter that balance to anticipate stronger and weaker servers. NLB also supports affinity, so that particular IP addresses will consistently go to the same server.

NLB uses the concept of distributed algorithms and virtual IPs to operate. There is no central management point for NLB: Every server knows the rules for load balancing and they all listen on the same virtual IP to all the traffic. Based on the algorithmic rule, all servers know which server should respond to a given packet. To manage server failure, each server in the cluster sends out a "heartbeat," letting the other servers know it's still functioning. Should a server go down, within a minute the servers will reconfigure themselves to cover all traffic without the missing server.

Hardware load balancers can do the same tricks that NLB can do and more. They can handle larger amounts of traffic, and they have more sophisticated load-balancing options, such as using agents to receive current load data from each server so that the least-loaded server gets the next request. Hardware load balancers are generally purchased in pairs for failover purposes. Their sophisticated options come at a price, of course, but for larger, busier Web farms, they are indispensable.

Payload Reduction
Several companies, including Cisco, Citrix Systems, F5 Networks, and Strangeloop offer solutions for reducing the size of the Web page. These solutions use compression to reduce payload size and also can accelerate the performance of secure sites by offloading processor-intensive SSL encryption and decryption tasks. These appliance-based optimization solutions are designed to work with all types of Internet applications, regardless of the platform or development technology, and can free up significant network bandwidth and server resources.

You can also look to third-party software components, such as Flesk's ViewStateOptimizer, to replace ASP.NET's default ViewState management function. By stripping ViewState data from each response and reinserting it into subsequent corresponding requests, these solutions reduce the payload sent to and from the browser. To use this solution, you still have to modify your code to call into the Flesk component for ViewState persistence, but it does allow you to configure your application to store ViewState data in session state variables or in disk files (see Figure 1), and employ automatic data compression.

The Strangeloop AS1000 appliance provides a solution to ViewState data by storing the data on the appliance - since the appliance sits in front of the Web servers (see Figure 2), it can automatically remove the ViewState from the Web page on the way to the browser from the Web server and reinsert the data as the postback comes from the browser to the Web server.

Caching
As an alternative to ASP.NET's built-in caching features, several companies now offer appliances that can provide a plug-and-play alternative for caching both static and dynamic content, without requiring you to change your application code. Both the F5 WebAccelerator appliance and the Strangeloop AS1000, for example, automatically identify static content such as images and adjust HTTP headers to increase browser caching of this content.

Beyond browser caching, the Strangeloop AS1000 automatically learns what Web pages are cacheable based on their frequency of change and can automatically cache even dynamic Web pages, creating an effective alternative to ASP.NET's more manual-intensive output caching features.

Acceleration Services
The product-based optimizations I've mentioned so far are all solutions that run in your data center, but you can also take advantage of third-party acceleration services. For example, a growing number of sites use the Akamai EdgePlatform, a network of 200,000 worldwide servers, to optimize caching and accelerate the delivery of images and other static and dynamic content.

You can also use vendor-based services to handle streaming media. Akamai, NaviSite, OnStream Media, and others provide a range of services for streaming audio and video content that can otherwise rapidly consume your network, memory, and processor resources. Adobe also offers a service specifically geared to optimize Flash media delivery, and Microsoft offers a similar service for Silverlight streaming.

Embracing Product-Based Strategies for Optimizing Applications
The ultimate scalability and performance of your ASP.NET application depends on a variety of factors, and you're not going to be able to "buy" your way out of every bottleneck you may encounter. But for a growing number of performance issues, third-party solutions can offer a compelling alternative to manually tuning your code. Product- and service-based optimizations can provide simple drop-and-go solutions for many performance challenges, without forcing you to forgo the productivity-enhancing programming techniques you need to get your application out the door quickly. Most important, they eliminate the question marks associated with any code-based optimization project and provide a known return for a known cost.

© 2008 SYS-CON Media