10 Tips For Writing High-Performance Web Application
10 Tips For Writing High-Performance Web Application
NET
10 Tips for Writing High-
Performance Web Applications
Rob Howard
In Community Server, we wrote a paging server control to do all the data paging.
You'll see that I am using the ideas discussed in Tip 1, returning two resultsets from
one stored procedure: the total number of records and the requested data.
The total number of records returned can vary depending on the query being
executed. For example, a WHERE clause can be used to constrain the data returned.
The total number of records to be returned must be known in order to calculate the
total pages to be displayed in the paging UI. For example, if there are 1,000,000 total
records and a WHERE clause is used that filters this to 1,000 records, the paging logic
needs to be aware of the total number of records to properly render the paging UI.
Tip 3—Connection Pooling
Setting up the TCP connection between your Web application and SQL Server™ can
be an expensive operation. Developers at Microsoft have been able to take
advantage of connection pooling for some time now, allowing them to reuse
connections to the database. Rather than setting up a new TCP connection on each
request, a new connection is set up only when one is not available in the connection
pool. When the connection is closed, it is returned to the pool where it remains
connected to the database, as opposed to completely tearing down that TCP
connection.
Of course you need to watch out for leaking connections. Always close your
connections when you're finished with them. I repeat: no matter what anyone says
about garbage collection within the Microsoft®.NET Framework, always call Close or
Dispose explicitly on your connection when you are finished with it. Do not trust the
common language runtime (CLR) to clean up and close your connection for you at a
predetermined time. The CLR will eventually destroy the class and force the
connection closed, but you have no guarantee when the garbage collection on the
object will actually happen.
To use connection pooling optimally, there are a couple of rules to live by. First, open
the connection, do the work, and then close the connection. It's okay to open and
close the connection multiple times on each request if you have to (optimally you
apply Tip 1) rather than keeping the connection open and passing it around through
different methods. Second, use the same connection string (and the same thread
identity if you're using integrated authentication). If you don't use the same
connection string, for example customizing the connection string based on the
logged-in user, you won't get the same optimization value provided by connection
pooling. And if you use integrated authentication while impersonating a large set of
users, your pooling will also be much less effective. The .NET CLR data performance
counters can be very useful when attempting to track down any performance issues
that are related to connection pooling.
Whenever your application is connecting to a resource, such as a database, running
in another process, you should optimize by focusing on the time spent connecting to
the resource, the time spent sending or retrieving data, and the number of round-
trips. Optimizing any kind of process hop in your application is the first place to start
to achieve better performance.
The application tier contains the logic that connects to your data layer and
transforms data into meaningful class instances and business processes. For
example, in Community Server, this is where you populate a Forums or Threads
collection, and apply business rules such as permissions; most importantly it is where
the Caching logic is performed.
One of the most common myths is that C# code is faster than Visual Basic
code. There is a grain of truth in this, as it is possible to take several performance-
hindering actions in Visual Basic that are not possible to accomplish in C#, such as
not explicitly declaring types. But if good programming practices are followed, there
is no reason why Visual Basic and C# code cannot execute with nearly identical
performance. To put it more succinctly, similar code produces similar results.
Another myth is that codebehind is faster than inline, which is absolutely false. It
doesn't matter where your code for your ASP.NET application lives, whether in a
codebehind file or inline with the ASP.NET page. Sometimes I prefer to use inline
code as changes don't incur the same update costs as codebehind. For example, with
codebehind you have to update the entire codebehind DLL, which can be a scary
proposition.
Myth number three is that components are faster than pages. This was true in Classic
ASP when compiled COM servers were much faster than VBScript. With ASP.NET,
however, both pages and components are classes. Whether your code is inline in a
page, within a codebehind, or in a separate component makes little performance
difference. Organizationally, it is better to group functionality logically this way, but
again it makes no difference with regard to performance.
The final myth I want to dispel is that every functionality that you want to occur
between two apps should be implemented as a Web service. Web services should be
used to connect disparate systems or to provide remote access to system
functionality or behaviors. They should not be used internally to connect two similar
systems. While easy to use, there are much better alternatives. The worst thing you
can do is use Web services for communicating between ASP and ASP.NET
applications running on the same server, which I've witnessed all too frequently.
you can effectively generate the output for this page once and reuse it multiple times
for up to 60 seconds, at which point the page will re-execute and the output will once
be again added to the ASP.NET Cache. This behavior can also be accomplished using
some lower-level programmatic APIs, too. There are several configurable settings for
output caching, such as the VaryByParams attribute just described. VaryByParams
just happens to be required, but allows you to specify the HTTP GET or HTTP POST
parameters to vary the cache entries. For example, default.aspx?Report=1 or
default.aspx?Report=2 could be output-cached by simply setting
VaryByParam="Report". Additional parameters can be named by specifying a
semicolon-separated list.
Many people don't realize that when the Output Cache is used, the ASP.NET page
also generates a set of HTTP headers that downstream caching servers, such as
those used by the Microsoft Internet Security and Acceleration Server or by Akamai.
When HTTP Cache headers are set, the documents can be cached on these network
resources, and client requests can be satisfied without having to go back to the origin
server.
Using page output caching, then, does not make your application more efficient, but
it can potentially reduce the load on your server as downstream caching technology
caches documents. Of course, this can only be anonymous content; once it's
downstream, you won't see the requests anymore and can't perform authentication
to prevent access to it.
Tip 8—Run IIS 6.0 (If Only for Kernel Caching)
If you're not running IIS 6.0 (Windows Server™ 2003), you're missing out on some
great performance enhancements in the Microsoft Web server. In Tip 7, I talked
about output caching. In IIS 5.0, a request comes through IIS and then to ASP.NET.
When caching is involved, an HttpModule in ASP.NET receives the request, and
returns the contents from the Cache.
If you're using IIS 6.0, there is a nice little feature called kernel caching that doesn't
require any code changes to ASP.NET. When a request is output-cached by ASP.NET,
the IIS kernel cache receives a copy of the cached data. When a request comes from
the network driver, a kernel-level driver (no context switch to user mode) receives
the request, and if cached, flushes the cached data to the response, and completes
execution. This means that when you use kernel-mode caching with IIS and ASP.NET
output caching, you'll see unbelievable performance results. At one point during the
Visual Studio 2005 development of ASP.NET, I was the program manager responsible
for ASP.NET performance. The developers did the magic, but I saw all the reports on
a daily basis. The kernel mode caching results were always the most interesting. The
common characteristic was network saturation by requests/responses and IIS running
at about five percent CPU utilization. It was amazing! There are certainly other
reasons for using IIS 6.0, but kernel mode caching is an obvious one.
If you are not doing postbacks in a page or are always regenerating the controls on a
page on each request, you should disable view state at the page level.