HOWTO: Use the HTTP.SYS Kernel Mode Response Cache with IIS 6

Motivation

One of the bigger buzz-word features of IIS 6.0 on Windows Server 2003 is the "HTTP.SYS Kernel Mode Response Cache".

When you do a search against "HTTP.SYS Kernel Response Cache IIS 6", you will inevitably find a large body of literature  repeatedly talking about how the kernel mode response cache effectively improves performance and lightens server load by removing the kernel/user mode transitions, improves request/response latency, improves... blah blah blah blah blah... and is the best thing since sliced bread.

Unfortunately, there is not a lot of concentrated, pragmatic information which focuses on the internals of how this cache works so that one can effectively take advantage of this feature. Specially, from within the context of IIS 6.0 and ASP.NET by association since it is an ISAPI Extension DLL.

This lack of consolidated information, along with several newsgroup questions about how the kernel mode response cache works, is what motivated me to write this entry about the background and the raw mechanics of how to leverage this cache from IIS 6.0.

Cache Basics

The best way to understand a cache is by understanding how to:

  1. Place an item into the cache (cache insertion policy).
  2. Remove an item from the cache (cache eviction policy).

Now, how does this translate to IIS 6.0 using HTTP.SYS?

Cache Insertion Policy

There are two ways for IIS 6.0 to insert an item into the HTTP.SYS kernel mode response cache:

  1. The IIS Static File Handler. If you make two requests for the same resource handled by the static file handler within a 10 second contigous interval (configurable via the ActivityPeriod registry key), the resource will be placed into the kernel mode response cache.
  2. ISAPI Extension calls HSE_REQ_VECTOR_SEND to send a complete HTTP response which includes at least the HSE_IO_SEND_HEADERS, HSE_IO_FINAL_SEND, and HSE_IO_CACHE_RESPONSE flags, along with a Last-Modified: and optionally an Expires: response header.

You must realize that just applying the cache insertion policy does NOT ensure that HTTP.SYS actually caches the response. There are many other variables that affect the cacheability of the response, as documented at this URL.

Of course, there are also limits on what can be placed into the kernel response cache. The most interesting variables from an ISAPI perspective are:

  1. Response Size - 256KB by default and controlled by the registry key HKLM\SYSTEM\CurrentControlSet\Services\HTTP\Parameters\UriMaxUriBytes
  2. Scavenger - 120 seconds by default and controlled by the registry key HKLM\SYSTEM\CurrentControlSet\Services\HTTP\Parameters\UriScavengerPeriod

For more info on other registry key tweaks, see:

Personally, I would not tweak with the registry keys unless you know you need to.

Cache Eviction Policy

The HTTP.SYS kernel response cache, as exposed by IIS, has a very simple cache entry eviction policy - whichever of the following criteria triggers first will flush the associated cache entry(s):

  1. TTL (Time-To-Live) - Set by the Expires: header in the response.

  2. HTTP.SYS Scavenger - UriScavengerPeriod controls the frequency with which the scavenger will evict any cache entry that has not been accessed in the last UriScavengerPeriod amount of time, in seconds.

  3. On-Demand - ISAPI Extension can use the HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK ServerSupportFunction to retrieve a pointer to the on-demand URL revocation function, and you call this function pointer with the FULL URL (obtained via the UNICODE_CACHE_URL ServerVariable when you cached that URL's response to begin with) to evict that URL's cached response.

    For example, suppose your ISAPI decides to use HSE_REQ_VECTOR_SEND to cache a certain response. If you want to be able to evict it later, using your own cache policy, you want to retrieve and keep the UNICODE_CACHE_URL ServerVariable when you make your HSE_REQ_VECTOR_SEND call. Then, when you want to evict that entry for whatever reason, you call call the cache invalidation callback using that saved UNICODE_CACHE_URL value.

Now, a subtle point with the timing-related eviction policies. HTTP.SYS does NOT immediately evict cache entries when their time is up. Instead, it sweeps through the response cache with a 30 second granularity. What this means is that suppose UriScavengerPeriod is set to 60 seconds. Cache items can between 60 and 89 seconds old, depending on when the cache item was inserted relative to the last periodic sweep.

However, On-Demand invocation is immediate - as soon as you call it, you will get a cache-miss if it used to be a cache-hit.

Conclusion

I hope that my explanation helps clear up the roadmap of how to use this exciting HTTP.SYS feature. If there are any unclear points, feel free to post comments for the benefits of everyone. I will try and follow up with all comments to make this information useful.

Some final observations:

  1. A static file (i.e. .htm, .css, .js, .jpg) is NOT guaranteed to be kernel response cached. You could have an [Wildcard] Application Mapping that applies to those extensions, in which case it depends on the cache insertion policy applied by that Application Mapping (as well as the other cacheability factors that I described earlier) when it handled the request and generated a response.
  2. A dynamic page can be kernel response cached IF the script engine that handled the request uses the ISAPI cache insertion policy mentioned above.

//David