Article
Cost-Effective Website Acceleration
Part 2 - Effective Cache Control
In Part I of this series, we introduced the two basic laws of Web performance:
- Send as little data as possible
- Send it as infrequently as possible
In the first installment, we focused on rule one and offered twenty tips to squeeze every byte out of delivered pages through code optimization, looking well beyond the obvious bandwidth-hogging images, to JavaScript, HTML, CSS, and even file name optimizations.
In this installment, we will focus primarily on rule number two, by means of better utilization of caching on the Web. Once you start to design your sites with an eye towards effective caching control, you will dramatically reduce page load times for your users -- particularly your most loyal, repeat visitors -- while lowering your overall bandwidth consumption and freeing up your server resources.
The Many Types of Caches on the Web
The basic idea behind caching is simple: instead of wasting efforts by re-downloading a resource every time it is needed, keep a local copy, and reuse it for as long as it is still valid. The most common example of Web caching is the browser cache, which stores copies of images and other page objects on an end user's hard drive for repeated use.
Though there are many other caches on the Web -- at origin Web servers, along the network path, and even on the end user's local network -- the purposes of all types of cache are essentially the same. Looking out from a local browser cache, you might next encounter a proxy cache on your local network, implemented so that other users on the LAN do not have to access the Web to fetch the same site as you. Next, your ISP or various transit ISPs further down the line might also employ a proxy cache to serve a site's objects to visitor on their network. Finally, the actual Website may utilize a reverse proxy cache to hold generated pages in finished form, ready for delivery, in order to relieve the server of the burden of repeatedly generating and delivering frequently requested pages.
We can categorize all Web caches into two general varieties: private and public. A private cache, most commonly a Web browser, is unique to a single user agent and is used to store items that should only be available for reuse by an individual end user. On the other hand, proxy and reverse proxy caches are public caches. These are used for shared resources -- those items that can safely be reused by more than one end user. The figure below shows the common cache types:

Cache Usage on the Web
This diagram illustrates a key point in our discussion: caches are found on the Web in many places and are constantly trying to hold your site content whenever possible. While it's easy to remain ignorant and allow them to dictate caching behavior, from the standpoint of site performance, it is vital to engage the different forms of cache purposefully, dictating which objects should or should not be cached, and for how long.
Freshness and Validation
In order to make the best use of any cache, including browser cache, we need to provide some indication when a resource is no longer valid and should therefore be reacquired. More specifically, we need the ability to indicate caching rules for Web page objects, ranging from setting appropriate expiration times, to indicating when a particular object should not be cached at all. Fortunately, we have all these capabilities at our fingertips in the form of HTTP cache controls rules.
The key to cache awareness lies in understanding the two concepts that govern how caches behave: freshness and validation. Freshness refers to whether or not a cached object is up-to-date, or, in more technical terms, whether or not a cached resource is in the same state as that same resource on the origin server. If the browser or other Web cache lacks sufficient information to confirm that a cached object is fresh, it will always err on the side of caution and treat it as possibly out-of-date or stale. Validation is the process by which a cache checks with the origin server to see whether one of those potentially stale cached object is fresh or not. If the server confirms that the cached object is still fresh, the browser will use the local resource.
A Basic Example of Caching
The concepts of freshness and validation are best illustrated with an example (in this case using a browser cache, but the core principles hold true to public caches as well):
Step 1
A remote site contains a page called page1.html. This page references image1.gif, image2.gif, and image3.gif and has a link to page2.html. When we access this page for the first time, the HTML and the associated GIF images are downloaded one-by-one and stored in the local browser cache.

Initial Cache Load
Once the data is downloaded to the cache, it is "stamped" to indicate where it came from and at what time it was accessed. It may also be stamped with a third piece of information: when it needs to be reacquired. But, as most sites do not stamp their data with this explicit cache control information, we'll assume that our example lacks this information.
Step 2
The user follows the link to page2.html, which has never been visited before, and which references image1.gif, image3.gif, and image4.gif. In this case, the browser downloads the markup for the new page, but the question is: should it re-download image1.gif and image3.gif even though it already has them cached? The obvious answer would be no, but, how can we be sure that the images have not changed since we downloaded page1.html? Without cache control information, the truth is that we can't.
Therefore, the browser would need to revalidate the image by sending a request to the server in order to check whether each image has been modified. If it has not been changed, the server will send a quick "304 Not Modified" response that instructs the browser to go ahead and use the cached image. But, if it has been modified, a fresh copy of the image will have to be downloaded. This common Not Modified request-and-response cycle is shown here:

Cache Check
From this basic example, it is apparent that, even when CSS, images, and JavaScript are fresh, we may not get the caching benefit we expect, since the browser still has to make a round trip to the server before it can reuse the cached copy.
The default "Automatic" setting in Internet Explorer partially reduces this continual chatter between browser and server by skipping the revalidation of cached objects during a single browser session. You will notice that page load time is generally much quicker when you revisit the same page during the same browser session. To see the performance penalty that would otherwise be incurred by all those 304 Not Modified responses, instead select "Every visit to the page."

IE's Cache Control Dialog
Note: While IE's "smart caching" does cut down on unnecessary validation requests, it is also behind IE's continual reminders to users to clear their caches in order to see new content. With caching, there is a trade-off for everything!
The Benefits of Caching
Minimizing round trips over the Web to revalidate cached items can make a huge difference in browser page load times. Perhaps the most dramatic illustration of this occurs when a user returns to a site for the second time, after an initial browser session. In this case, all page objects will have to be revalidated, each costing valuable fractions of a second (not to mention consuming bandwidth and server cycles). On the other hand, utilizing proper cache control allows each of these previously viewed objects to be served directly out of the browser's cache without going back to the server.
The effect of adding cache rules to page objects is often visible at page load time, even with a high bandwidth connection, and users may note that your sites appear to paint faster and that "flashing" is reduced between subsequent page loads. Besides improved user perception, the Web server will be offloaded from responding to cache revalidation requests, and thus will be able to better serve new traffic.
However, in order to enjoy the benefits of caching, a developer needs to take time to write out a set of carefully crafted cache control policies that categorize a site's objects according to their intended lifetimes. Here is an example of a complete set of cache control policies for a simple ecommerce Website:

As you can see, from the point of view of cache control, this site has six different types of objects. As the logo and other corporate branding is unlikely to change, navigational and logo images are treated as virtually permanent. Cascading Style Sheets and JavaScript files are given freshness lifetimes to support a regular, semi-annual update schedule. As fresh site content is important in terms of search engine optimization and user experience, the main header images are set up to be changed a bit more frequently. The monthly "special offer" image is, of course, designed to stay fresh for one month. There is also a personalized special offer image that remains fresh in a user's cache for two weeks after the initial visit; note that this category is marked "private" to indicate that it is not to be cached by a shared/proxy cache. Finally, the default policy for everything else on the site states that nothing else should be cached, which guarantees that text and dynamic content is served fresh for each request.
You need to be very careful not to cache HTML pages, whether or not they are statically generated, unless you really know what you're doing. If anything, you should use cache control to make sure these pages are not cached. If a user caches your HTML page, and you set a lengthy expiration time, they will not see any of the content changes you may make until the cached object expires. On the other hand, if you focus on caching dependent objects, such as images, Flash files, JavaScript, and style sheets, you can replace that cached content simply by renaming the objects.
For example, let's say you have a policy to change your site's logo files once a year, but in the middle of that year, your company makes a significant branding change that needs to be reflected on the site. Fortunately, if you have not set your HTML files to be cached, you can still serve the new logo by renaming the file from logo.gif to newlogo.gif and changing the associated HTML <img> references. As the HTML is parsed, the browser will note that it does not have the new image in its cache and will download it. Of course, the old image will still be in the user's cache for quite some time, but it will no longer be used.
After you've considered carefully what site resources should and should not be cached, the next step is to implement those policies.