Parts in this series
Introduction
Using compression is the single most effective way to reduce page load times. The .aspx files sent by the server to the browser consist of HTML. HTML is highly compressible
by algorithms such as gzip. Because of this, modern web servers including IIS 5 and later have the ability to compress outgoing files, and modern browsers have
the ability to decompress incoming files.
Both IIS 6 and IIS 7 offer advanced compression related options that help you get better performance improvements for your web site and make better use of your
servers and bandwidth. Unfortunately, these options are not always easy to access. This article series shows step by step how to unlock these options.
In the first article in this two part series, we'll focus on configuring IIS 7 compression. If you are used to IIS 6, you'll find that IIS 7 offers many new features, including the ability to
cache not only compressed static files, but also compressed dynamic files. If you still use IIS 6, the next article in the series will
show how to configure IIS 6 compression.
This article is based on chapter 10 Compression of my book
ASP.NET Site Performance Secrets.
If you like this article, please vote for it.
Contents
Request and response headers involved in compression
How does the server know that the browser can accept compressed content? And how does the browser know that the content it received is compressed?
When a browser that supports compression sends a request to the server, it includes the request header
Accept-Encoding telling the server which compression algorithms it supports. For example:
Accept-Encoding: gzip,deflate
If the server then uses compression for its response, it includes the response header Content-Encoding in the (uncompressed) file header to say how the file
has been compressed, as shown:
Content-Encoding: gzip
This keeps the browser and server compression-wise in sync. However, it isn't only browsers and servers that send and receive requests and responses, but proxies as well. And proxies
can cache responses and serve subsequent requests from their cache. When a proxy caches a compressed file, how do we make sure that the proxy doesn't send
that compressed file to a browser that can't process compressed files?
The solution adopted by IIS 6 and IIS 7 is to tell the proxy that if it receives a request without the Accept-Encoding request header, it must not serve a file that was sent
in response to a request that did have the Accept-Encoding request header, or vice versa, that is, the Accept-Encoding request
headers must match. IIS 6 and 7 make this happen by sending a Vary header in the response from the server when compression is enabled as shown:
Vary: Accept-Encoding
IIS 6 also lets you override the Cache-Control and Expires headers for compressed files via properties in its metabase. This allows you to suppress
proxy caching for compressed files. The IIS 6 metabase will be described in part 2, about configuring IIS 6 compression.
The metabase properties that override the Cache-Control and Expires headers can be found here.
Starting configuration of IIS 7 compression
Before you start configuration of IIS 7 compression, find out at the following sites whether your pages already use compression, and if so, how much bandwidth you're saving:
Enabling compression in IIS 7 essentially consists of these steps:
- Installing the dynamic content compression module;
- Enabling compression;
- Configuring advanced features.
Let's go through these steps one by one.
Installing the dynamic content compression module
If you want to use compression for dynamic files, first install the dynamic content compression module. The steps to do this are different depending on whether
you use Vista/Windows 7 or Windows Server 2008.
On Windows Server 2008:
- Click Start | Administrative Tools | Server Manager.
- On the left-hand side, expand Roles and then click on Web Server (IIS).
- Scroll down to the Role Services section and then click on Add Role Services. The Add Role Services wizard opens:
- On the Select Role Services page, scroll down to the Performance section and select Dynamic Content Compression. Click on Next.
- Read the message and click Install.
- Once the installation is done, close the wizard.
On Vista or Windows 7:
- Click on Start | Control Panel | Programs | Turn Windows features on or off. The Windows Features dialog opens.
- Expand Internet Information Services, expand World Wide Web Services, and expand Performance Features. Select Http
Compression Dynamic.
- Click on OK. Wait for the feature to be configured.
Enabling compression
Now enable compression in the IIS Manager:
- Open IIS Manager. Click on Start | Control Panel. Type admin in the search box. Click on Administrative Tools.
Double-click on Internet Information Services (IIS) Manager.
- Click on your machine. Then double-click on the Compression icon on the right-hand side.
- The compression window opens. Here you can enable compression for dynamic content and static content. The window shows the following items:
- Enable dynamic content compression: Unless your server already uses a lot of CPU, you will want to enable dynamic content compression.
- Enable static content compression: You can safely enable static content compression because
compressed static content gets cached. So, only the initial compression takes CPU cycles.
- Only compress files larger than (in bytes): It makes sense to not compress small files. Because compression produces some
overhead in the file, compressing a small file may actually make it bigger rather than smaller.
- Cache directory: This is where compressed static files are stored. If you are short on disk space on the system drive, consider putting this on another drive.
Make sure that the drive is a local drive or NTFS partition, and that it isn't compressed or shared.
- Per application pool disk space limit (in MB): If you have lots of application pools
and limited disk space, you may want to adjust this. If you have 100 application pools and you leave this at 100MB, 100 x 100MB = 10GB may be used to cache static compressed files.
- On the right-hand side of the window, click on Apply. Compression is now enabled.
Setting compression by site, folder, or file
In addition to enabling or disabling compression for all sites on the server, you can enable or disable compression at a site level, or even a folder or file level.
To make this work:
- Open the IIS Manager and in the left-hand side, click on the site, folder, or file whose compression status you want to change.
- Make sure that the middle pane is switched to Features View, and double-click on the Compression icon.
- This will open a window where you can enable or disable compression for dynamic or static files:
Compression level
You can tweak the tradeoff between compression and CPU usage by setting the compression level. The higher the compression level, the greater the compression and CPU usage.
The compression level can be set separately for static and dynamic files. For static files, use 9, the highest level. For dynamic files, compression level 4 seems to be the sweet spot,
as shown in this study.
However, the optimal compression level for your website may be different, depending on how much spare CPU capacity you
have, the compressibility of your pages, and your bandwidth costs. Experiment with different levels to see which one works best for you.
To set the compression level:
- Execute this from the command prompt:
C:\Windows\System32\Inetsrv\Appcmd.exe
set config -section:httpCompression
-[name='gzip'].staticCompressionLevel:9
-[name='gzip'].dynamicCompressionLevel:4
(This sets compression level 9 for static files and compression level 4 for dynamic files).
- Reset the IIS server to make the new compression level take effect. In IIS Manager, click on the server at the top of the tree
and then click on Restart on the right-hand side.
Disabling compression based on CPU usage
To make sure that compression doesn't overload the CPU, IIS 7 calculates average CPU usage every 30 seconds. It automatically switches off
compression when CPU usage exceeds a given limit. Then when CPU usage drops below a second limit, it switches on compression again.
The default values for these limits are:
| Switch compression off at (CPU usage) | Switch back on at (CPU usage) |
---|
Dynamic files | 90 percent | 50 percent |
Static files | 100 percent | 50 percent |
Note that this means that if CPU usage on your server is consistently over 50 percent, and when it spikes over 90 percent,
compression for dynamic files will be switched off, but will never be switched back on again.
You can change these limits by modifying the applicationHost.config file, which is normally in the folder C:\Windows\System32\inetsrv\config:
- Make a backup copy of applicationHost.config.
- Open applicationHost.config with a text editor.
- Find the
<httpCompression>
section. - To change the CPU usage at which compression for dynamic files is switched back on to 70 percent, add the
dynamicCompressionEnableCpuUsage
attribute to the httpCompression
element, as shown:
<httpCompression dynamicCompressionEnableCpuUsage="70" .... >
Note that you provide a number to the attribute, not a percentage, so don't write a percentage sign when setting the attribute.
The value 70 shown here is simply an example, not a recommendation. You need to determine the optimal value for your own site.
- Save the applicationHost.config file.
- Reset the IIS server to make the new compression level take effect. Start IIS Manager, click on the server at the top of the
tree, and then click on Restart on the right-hand side.
In case you want to change any of the other limits, here are the matching attributes:
| Switch compression off at (CPU usage) | Switch back on at (CPU usage) |
---|
Dynamic files | dynamicCompressionDisableCpuUsage | dynamicCompressionEnableCpuUsage |
Static files | staticCompressionDisableCpuUsage | staticCompressionEnableCpuUsage |
If you want to stop IIS from ever switching off compression based on CPU usage, set all these attributes to 100.
You will find all the elements and attributes that can be used with httpCompression
here.
Setting the request frequency threshold for static compression
As you saw earlier, IIS 7 caches the compressed versions of static files. So, if a request arrives for a static file
whose compressed version is already in the cache, it doesn’t need to be compressed again.
But what if there is no compressed version in the cache? Will IIS 7 then compress the file right away and put it
in the cache? The answer is yes, but only if the file is being requested frequently. By not compressing files that are only requested infrequently, IIS 7 saves CPU usage and cache space.
By default, a file is considered to be requested frequently if it is requested two or more times per 10 seconds.
This is determined by two attributes in the serverRuntime
element in web.config:
serverRuntime attribute | Description |
---|
frequentHitThreshold | Number of times a URL must be requested within the time span specified in the frequentHitTimePeriod
attribute to be considered frequently hit. Must be between 1 and 2147483647. Default is 2. |
frequentHitTimePeriod
| Time interval in which a URL must be requested the number of times specified in the frequentHitThreshold
attribute before it is considered to be frequently hit. Default is 10 seconds. |
This means that when a static file is requested for the very first time, it won’t be compressed.
For example, to specify that static files need to be hit seven times per 15 seconds before they will be compressed, use:
<configuration>
...
<system.webServer>
<serverRuntime frequentHitThreshold="7" frequentHitTimePeriod="00:00:15" />
</system.webServer>
...
</configuration>
Caching compressed dynamic files
You've seen that IIS 7 caches only the compressed version of static files, and that dynamic files are compressed for each request (provided that
dynamic file compression is enabled). This means that compressing dynamic files takes much more CPU than static files.
That makes sense if the dynamic files are different for each visitor, for example, if each page contains personal information. However, if the
dynamic pages are fairly static and the same for all visitors, it makes sense to cache their compressed versions too.
You may already use the ASP.NET OutputCache
directive to cache your .aspx pages. The issue is that by default, IIS stores the uncompressed version of the file in the output cache, rather than the compressed version. For each
request, IIS then has to compress the contents of the cache before sending it to the browser. This is not very efficient.
Storing compressed files in the output cache
Here is how to get IIS to cache the compressed version of the file, rather than the uncompressed version. That way, it doesn't have to compress the
file for each request, reducing CPU usage.
Because this uses ASP.NET output caching, you need to use the OutputCache
directive in your pages, as shown:
<%@ OutputCache Duration="300" VaryByParam="none" %>
This caches the page for 300 seconds.
Now to get IIS to cache the compressed version rather than the uncompressed version, modify the applicationHost.config file.
You'll normally find this file in the folder C:\Windows\System32\inetsrv\config:
- Make a backup copy of applicationHost.config.
- Open applicationHost.config with a text editor.
- Find the
<urlCompression>
section. - Add the
dynamicCompressionBeforeCache="true"
attribute to the urlCompression
element, as shown:
<urlCompression dynamicCompressionBeforeCache="true" ... />
- Save the applicationHost.config file.
- Reset the IIS server to make the new attribute take effect.
- Start IIS Manager, click the server at the top of the tree, and then click Restart on the right-hand side.
What if a client doesn't accept compressed content?
Now that we're caching compressed content, what happens if someone visits your site with a browser that doesn't accept compressed content?
To simulate this eventuality, let's send a request to the server that doesn't have the Accept-Encoding request header. This should force the server
to send uncompressed content.
To do this, we'll use Fiddler, a free proxy which allows you to "fiddle" with requests and responses while
they are travelling between browser and server. It's easiest to do this with Firefox:
- Open Firefox and download Fiddler at http://www.fiddler2.com/fiddler2/.
- Install Fiddler and start it.
- On the Firefox status bar, switch on forcing traffic to Fiddler.
- At this stage, when you visit a page with Firefox, the server should still use compression (assuming the request has an Accept-Encoding request header allowing gzip compression).
You can measure the actual size of your compressed pages as they will travel over the Internet to the browser using the Web Developer add-on for Firefox:
- Using Firefox, visit http://chrispederick.com/work/web-developer to download and install the Web Developer add-on.
- After you have installed Web Developer, load the page again.
- Right click anywhere in the page. A popup menu will appear. Click Web Developer | Information | View Document Size.
A new window appears showing the groups of files making up the page.
- Expand the Documents group to see the size of the page. If it was compressed while travelling over the Internet, you will also see its compressed size.
- Now get Fiddler to strip off the Accept-Encoding request header from the request going from Firefox to the web server. In the Fiddler window on the right-hand side,
click on the Filters tab, select Use Filters, select Delete request header, and type in Accept-Encoding.
- Refresh the page in Firefox. Check the file size again with Web Developer. You should find that no compression was used with
this request. That will make browsers that do not support compression happy. So far, so good.
- In Fiddler, uncheck the Delete request header checkbox. As a result, the Accept-Encoding request header now makes it to the web server again.
- Refresh the page. The server should now compress the file again. But if you check with Web Developer, you'll find that it is still sending files uncompressed!
This is because when IIS received the request for uncompressed content, it threw away the compressed contents in the cache, regenerated the
content, and stored it uncompressed in the cache. It then keeps serving this uncompressed content until the cache expires, even to clients that accept compressed content.
- You can prevent this from happening by caching both compressed and uncompressed content. You do that by including
VaryByContentEncoding
in the OutputCache
directive,
as shown in the following code:
<%@ OutputCache Duration="300" VaryByParam="none"
VaryByContentEncoding="gzip;deflate" %>
- If you now delete the Accept-Encoding header and then let it go through again, you'll see that the server
always sends compressed content to clients that accept it, even if another client didn't accept it.
- Before you end this experiment and close Fiddler, go back to the Firefox status bar and stop sending traffic to Fiddler.
Otherwise Firefox will complain about the missing proxy when you close Fiddler.
A drawback of using VaryByContentEncoding
in the OutputCache
directive is that it disables
kernel caching for this file. Kernel caching is a highly efficient form of server side caching,
and is discussed in Chapter 5 of my book
ASP.NET Site Performance Secrets.
So should you use VaryByContentEncoding
in the OutputCache
directive? Seeing that you are reading this chapter, the gain in compression by using
VaryByContentEncoding
may well outweigh the loss of kernel caching, especially seeing that you already use output caching. Your best bet would be to try both scenarios
in production for a while, and compare CPU usage, response times, and bandwidth used per request for each scenario.
Improving the compressibility of your pages
If your server uses compression, then it makes sense to optimize compressibility of your .aspx, JavaScript, and CSS files.
Compression algorithms like repeating content, which puts a premium on consistency:
- Always specify HTML attributes in the same order. One way to achieve this is to have all your HTML generated by high-level web
controls and custom server controls, instead of low level HTML server controls. This will slightly increase CPU usage, but will give
you assured consistency. For example, write the following:
<asp:Hyperlink runat="server"......>
Instead of:
<a runat="server"......>
Likewise, within CSS selectors, write your properties in alphabetical order.Use consistent casing. Use all lowercase for HTML tags and attributes, and you'll be XHTML compliant as well.Use consistent quoting: Don't mix "...." and '....'.
Summary
In this article, we saw how to configure compression on IIS 7, including its more advanced, lesser known features.
In the next article, I'll show how to get the most out of the compression features built into IIS 6.
If you enjoyed this series and want to know the full story on how to improve ASP.NET site performance, from database server to web server to browser,
consider my book ASP.NET Site Performance Secrets.
Or visit my web site ASP.NET Performance.