Call to improve browser caching

April 26, 2010 9:14 pm | 38 Comments

Over Christmas break I wrote Santa my browser wishlist. There was one item I neglected to ask for: improvements to the browser disk cache.

In 2007 Tenni Theurer and I ran an experiment to measure browser cache stats from the server side. Tenni’s write up, Browser Cache Usage – Exposed, is the stuff of legend. There she reveals that while 80% of page views were done with a primed cache, 40-60% of unique users hit the site with an empty cache at least once per day. 40-60% seems high, but I’ve heard similar numbers from respected web devs at other major sites.

Why do so many users have an empty cache at least once per day?

I’ve been racking my brain for years trying to answer this question. Here are some answers I’ve come up with:

  • first time users – Yea, but not 40-60%.
  • cleared cache – It’s true: more and more people are likely using anti-virus software that clears the cache between browser sessions. And since we ran that experiment back in 2007 many browsers have added options for clearing the cache frequently (for example, Firefox’s privacy.clearOnShutdown.cache option). But again, this doesn’t account for the 40-60% number.
  • flawed experiment – It turns out there was a flaw in the experiment (browsers ignore caching headers when an image is in memory), but this would only affect the 80% number, not the 40-60% number. And I expect the impact on the 80% number is small, given the fact that other folks have gotten similar numbers. (In a future blog post I’ll share a new experiment design I’ve been working on.)
  • resources got evicted – hmmmmm

OK, let’s talk about eviction for a minute. The two biggest influencers for a resource getting evicted are the size of the cache and the eviction algorithm. It turns out, the amount of disk space used for caching hasn’t kept pace with the size of people’s drives and their use of the Web. Here are the default disk cache sizes for the major browsers:

  • Internet Explorer: 8-50 MB
  • Firefox: 50 MB
  • Safari: everything I found said there isn’t a max size setting (???)
  • Chrome: < 80 MB (varies depending on available disk space)
  • Opera: 20 MB

Those defaults are too small. My disk drive is 150 GB of which 120 GB is free. I’d gladly give up 5 GB or more to raise the odds of web pages loading faster.

Even with more disk space, the cache is eventually going to fill up. When that happens, cached resources need to be evicted to make room for the new ones. Here’s where eviction algorithms come into play. Most eviction algorithms are LRU-based – the resource that was least recently used is evicted. However, our knowledge of performance pain points has grown dramatically in the last few years. Translating this knowledge into eviction algorithm improvements makes sense. For example, we’re all aware how much costlier it is to download a script than an image. (Scripts block other downloads and rendering.) Scripts, therefore, should be given a higher priority when it comes to caching.

It’s hard to get access to gather browser disk cache stats, so I’m asking people to discover their own settings and share them via the Browser Disk Cache Survey form. I included this in my talks at JSConf and jQueryConf. ~150 folks at those conferences filled out the form. The data shows that 55% of people surveyed have a cache that’s over 90% full. (Caveats: this is a small sample size and the data is self-reported.) It would be great if you would take time to fill out the form. I’ve also started writing instructions for finding your cache settings.

I’m optimistic about the potential speedup that could result from improving browser caching, and fortunately browser vendors seem receptive (for example, the recent Mozilla Caching Summit). I expect we’ll see better default cache sizes and eviction logic in the next major release of each browser. Until then, jack up your defaults as described in the instructions. And please add comments for any browsers I left out or got wrong. Thanks.

38 Comments

P3PC: Collective Media

April 14, 2010 7:24 am | 8 Comments

P3PC is a project to review the performance of 3rd party content such as ads, widgets, and analytics. You can see all the reviews and stats on the P3PC home page. This blog post looks at Collective Media. Here are the summary stats.

impact on page Page Speed YSlow doc.
write
total reqs total xfer size JS ungzip DOM elems median Δ load time
big 86 90 y 6 8 kB 9 kB 7 na**
* Stats for ads only include the ad framework and not any ad content.
** It’s not possible to gather timing stats for snippets with live ads.
column definitions

I don’t have an account with Collective Media, so my friends over at Zimbio let me use their ad codes during my testing. Since these are live (paying) ads I can’t crowdsource time measurements for these ads.

Snippet Code

Let’s look at the actual snippet code:

1: <script type=”text/javascript” >
2: document.write(unescape(“%3Cscript src=’http://a.collective-media.net/adj/cm.zimbio/picture;sz=300×250;ord=” +  Math.round(Math.random()*10000000) + “‘ type=’text/javascript’%3E%3C/script%3E”));
3: </script>
4: <noscript><a href=”http://a.collective-media.net/jump/cm.zimbio/picture;sz=300×250;ord=[timestamp]?” target=”_blank”><img src=”http://a.collective-media.net/ad/cm.zimbio/picture;sz=300×250;ord=[timestamp]?” width=”300″ height=”250″ border=”0″ alt=”"></a></noscript>
snippet code as of April 13, 2010

Lines 1-3 use document.write to insert the a.collective-media.net/adj/cm.zimbio/picture script. Line 4 provides a NOSCRIPT block in case JavaScript is not available.

Performance Analysis

This HTTP waterfall chart was generated by WebPagetest.org using IE 7 with a 1.5Mbps connection from Dulles, VA. In my analysis of ad snippets I focus only on the ad framework, not on the actual ads. The Collective Media ad framework is composed of 6 HTTP requests: items 2, 3, 4, 5, 11&12, and 13.

Keep in mind that collective-media-waterfall.png represents the actual content on the main page. Notice how that image is pushed back to item 8 in the waterfall chart. In this one page load, this main content is blocked for 471 + 228 + 508 + 136 = 1343 milliseconds by the ad framework (and another 238 ms by the ad itself).

Let’s step through each request. The requests that are part of the ad framework are bolded.

  • item 1: compare.php – The HTML document.
  • item 2: a.collective-media.net/adj/cm.zimbio/picture – The main Collective Media script. This script is tiny – less than 400 bytes. It contains a document.write line that inserts the k.collective-media.net/cmadj/cm.zimbio/picture script (item 3).
  • item 3: k.collective-media.net/cmadj/cm.zimbio/picture – This was inserted as a script (by item 2). Instead of returning JavaScript code, it redirects to ak1.abmr.net/is/k.collective-media.net (item 4).
  • item 4: ak1.abmr.net/is/k.collective-media.net – This is a redirect from item 3 that itself redirects to k.collective-media.net/cmadj/cm.zimbio/picture (item 5).
  • item 5: k.collective-media.net/cmadj/cm.zimbio/picture – Most of the work of the Collective Media ad framework is done in this script. It dynamically inserts other scripts that contain the actual ad.
  • item 6: ad.doubleclick.net/adj/cm.zimbio/picture – A script that uses document.write to insert the actual ad.
  • item 7: adc_predlend_fear_300x250.jpg – The ad image.
  • item 8: collective-media-waterfall.png – The waterfall image representing the main page’s content.
  • item 9: favicon.ico – My site’s favicon.
  • item 10: cm.g.doubleclick.net/pixel – DoubleClick beacon.
  • item 11: l.collective-media.net/log – Collective Media beacon that fails.
  • item 12: l.collective-media.net/log – Retry of the Collective Media beacon.
  • item 13: a.collective-media.net/idpair – Another Collective Media beacon.

Items 2-5 are part of the ad framework. They have a dramatic impact on performance because of the way they’re daisy chained together:

Item 2 is a script that document.writes a request for item 3.
⇒ Item 3 redirects to item 4
⇒ Item 4 redirects to item 5 cach
⇒ Item 5 document.writes a request for item 6.

All of these requests are performed sequentially. This is the main reason why the main content in the page (collective-media-waterfall.png) is delayed 1343 milliseconds.

Here are some of the performance issues with this snippet.

1. The redirects cause sequential downloads.

A redirect is almost as bad as a script when it comes to blocking. The redirect from b.scorecardresearch.com/b to /b2 causes those two resources to happen sequentially. It would be better to avoid the redirect if possible.

2. The scripts block the main content of the page from loading.

It would be better to load the script without blocking, similar to what BuySellAds.com does. In this case there are two blocking scripts that are part of the ad framework (and more that are part of the actual ad).

3. The ad is inserted using document.write.

Scripts that use document.write slow down the page because they can’t be loaded asynchronously. Inserting ads into a page without using document.write can be tricky. BuySellAds.com solves this problem by creating a DIV with the desired width and height to hold the ad, and then setting the DIV’s innerHTML.

4. The beacon returns a 200 HTTP status code.

I recommend returning a 204 (No Content) status code for beacons. A 204 response has no body and browsers will never cache them, which is exactly what we want from a beacon. In this case, the image body is less than 100 bytes. Although the savings are minimal, using a 204 response for beacons is a good best practice.


There’s one other part of the Collective Media ad framework I’d like to delve into: how scripts are loaded.

The code snippet given to publishers loads the initial script using document.write. It appears this is done to inject a random number into the URL, as opposed to using Cache-Control headers:

1: <script type=”text/javascript” >
2: document.write(unescape(“%3Cscript src=’http://a.collective-media.net/adj/cm.zimbio/picture;sz=300×250;ord=” +  Math.round(Math.random()*10000000) + “‘ type=’text/javascript’%3E%3C/script%3E”));
3: </script>

That initial script (item 2 in the waterfall chart) returns just one line of JavaScript that does another document.write to insert a script (item 3), again inserting a random number into the URL:

1: document.write(‘<scr’+'ipt language=”javascript” src=”http://k.collective-media.net/cmadj/cm.zimbio/picture;sz=300×250;ord=2381217;ord1=’ +Math.floor(Math.random() * 1000000) + ‘;cmpgurl=’+escape(escape(cmPageURL))+’?”>’);  document.write(‘</scr’+'ipt>’);

That script request (item 3) is a redirect which leads to another redirect (item 4), which returns a heftier script (item 5) that starts to insert the actual ad. At the end of this script is a call to CollectiveMedia.createAndAttachAd. Here’s that function (unminified):

1: createAndAttachAd:function(h,c,a,d,e){
2:     var f=document.getElementsByTagName(“script”);
3:     var b=f[f.length-1];
4:     if(b==null){ return; }
5:     var i=document.createElement(“script”);
6:     i.language=”javascript”;
7:     i.setAttribute(“type”,”text/javascript”);
8:     var j=”";
9:     j+=”document.write(‘<scr’+'ipt language=\”javascript\” src=\”"+c+”\”></scr’+'ipt>’);”;
10:     var g=document.createTextNode(j);
11:     b.parentNode.insertBefore(i,b);
12:     appendChild(i,j);
13:     if(e){
14:         var k=new cmIV_();
15:         k._init(h,i.parentNode,a,d);
16:     }
17: },

In lines 2-7 & 11 a script element is created and inserted into the document. It’s debatable if you need to set the language (line 6) and type (line 7), but that’s minor. Using insertBefore instead of appendChild is a new pattern I’ve just started seeing that is more robust, so it’s nice to see that here. Lines 7-8 create a string of JavaScript to insert an external script using document.write. This could be one line, but again, that’s minor.

Then things get a little strange. Line 10 creates a text node element (“g”) that’s never used. In line 11 the script element is inserted into the document. Then a home built version of appendChild is called. This function is added to global namespace (ouch). Here’s what that function looks like:

1: function appendChild(a,b){
2:     if(null==a.canHaveChildren||a.canHaveChildren){
3:         a.appendChild(document.createTextNode(b));
4:     }
5:     else{
6:         a.text=b;
7:     }
8: }

OK. To wrap this up: A script element is created dynamically and inserted in the document. Then a string of JavaScript is injected into this script element. That line of JavaScript document.writes an external script request into the page. If that seems convoluted to you, you’re not alone. It took me awhile to wrap my head around this.

A cleaner approach would be to set the SRC property of the dynamic script element, rather than document.writing the script into the page. This would reduce the amount of code (small win), but more importantly avoiding document.write opens the door for loading ads asynchronously. This is what’s required to reach a state where ad content and publisher content co-exist equally in web pages.

8 Comments

P3PC: Glam Media

April 13, 2010 10:45 am | 2 Comments

P3PC is a project to review the performance of 3rd party content such as ads, widgets, and analytics. You can see all the reviews and stats on the P3PC home page. This blog post looks at Glam Media. Here are the summary stats.

impact on page Page Speed YSlow doc.
write
total reqs total xfer size JS ungzip DOM elems median Δ load time
big 89 83 y 11 68 kB 63 kB 7 na**
* Stats for ads only include the ad framework and not any ad content.
** It’s not possible to gather timing stats for snippets with live ads.
column definitions

I don’t have an account with Glam Media, so my friends over at Zimbio let me use their ad codes during my testing. Since these are live (paying) ads I have to mask the ad codes in the snippet shown here. This means it’s not possible to crowdsource time measurements for these ads.

Snippet Code

Let’s look at the actual snippet code:

1: <script type=”text/javascript” language=”javascript” src=”http://www2.glam.com/app/site/affiliate/viewChannelModule.act?mName=viewAdJs&affiliateId=123456789&adSize=300×250&zone=Marketplace”>
2: </script>
snippet code as of April 12, 2010

The Glam Media ad is kicked off from a single script: viewChannelModule.act. This script is loaded using normal SCRIPT SRC tags, which causes blocking in IE7 and earlier.

Performance Analysis

This HTTP waterfall chart was generated by WebPagetest.org using IE 7 with a 1.5Mbps connection from Dulles, VA. In my analysis of ad snippets I focus only on the ad framework, not on the actual ads. The Glam Media ad framework alone constitutes 9 HTTP requests.

Let’s step through each request.

  • item 1: compare.php – The HTML document.
  • item 2: viewChannelModule.act – The main Glam Media script.
  • item 3: ad.doubleclick.net – The actual ad (not included in my analysis).
  • item 4: glamadapt_jsrv.act – Script loaded by viewChannelModule.act using document.write.
  • item 5: quant.jsQuantcast script loaded by viewChannelModule.act using document.write.
  • item 6: beacon.jsScorecardResearch script loaded by viewChannelModule.act using document.write.
  • item 7: glam_comscore.js – Script loaded by viewChannelModule.act using document.write.
  • item 8: pixel – Beacon sent by quant.js.
  • item 9: b.scorecardresearch.com/b – Beacon sent by glam_comscore.js. This returns a redirect to /b2 (item 11).
  • item 10: glam-media-waterfall.png – The image representing the main page’s content.
  • item 11: altfarm.mediaplex.com/ad/js/ – The actual ad (not included in my analysis).
  • item 12: b.scorecardresearch.com/b2 – Another beacon sent as a result of the redirect from /b (item 9).

Keep in mind that glam-media-waterfall.png represents the actual content on the main page. Notice how that image is pushed back to item 10 in the waterfall chart. In this one page load, this main content is blocked for 617 + 808 = 1425 milliseconds. Here are some of the performance issues with this snippet.

1. Too many HTTP requests.

9 HTTP requests for an ad framework (not counting the ad itself) is a lot. The fact that these come from a variety of different services exacerbates the problem because more DNS lookups are required. These 9 HTTP requests are served from 6 different domains.

2. The scripts block the main content of the page from loading.

It would be better to load the script without blocking, similar to what BuySellAds.com does.

3. The ad is inserted using document.write.

Scripts that use document.write slow down the page because they can’t be loaded asynchronously. Inserting ads into a page without using document.write can be tricky. BuySellAds.com solves this problem by creating a DIV with the desired width and height to hold the ad, and then setting the DIV’s innerHTML.

4. The redirects cause sequential downloads.

A redirect is almost as bad as a script when it comes to blocking. The redirect from b.scorecardresearch.com/b to /b2 causes those two resources to happen sequentially. It would be better to avoid the redirect if possible.

5. Some resources aren’t cacheable.

glam_comscore.js has no caching headers, and yet its Last-Modified date is Nov 19, 2009 (almost 5 months ago). quant.js is only cacheable for 1 day.


Much of the content in this snippet is served with good performance characteristics. The scripts are compressed and minified. One of the beacons returns a 204 No Content response, which is a nice performance optimization. But the sheer number of HTTP requests, use of document.write, and scripts loaded in a blocking fashion cause the page to load more slowly.

2 Comments

P3PC: ValueClick

April 12, 2010 11:44 am | 1 Comment

P3PC is a project to review the performance of 3rd party content such as ads, widgets, and analytics. You can see all the reviews and stats on the P3PC home page. This blog post looks at ValueClick. Here are the summary stats.

impact on page Page Speed YSlow doc.
write
total reqs total xfer size JS ungzip DOM elems median Δ load time
med 89 98 y 3 2 kB 1 kB 1 na**
* Stats for ads only include the ad framework and not any ad content.
** It’s not possible to gather timing stats for snippets with live ads.
column definitions

I don’t have an account with ValueClick, so my friends over at Zimbio let me use their ad codes during my testing. Since these are live (paying) ads I have to mask the ad codes in the snippet shown here. This means it’s not possible to crowdsource time measurements for these ads.

Snippet Code

Let’s look at the actual snippet code:

1: <script language=”javascript” src=”http://media.fastclick.net/w/get.media?sid=12345&m=6&tp=8&d=j&t=s”></script>
2: <noscript><a href=”http://media.fastclick.net/w/click.here?sid=12345&m=6&c=1″ target=”_top”>
3: <img src=”http://media.fastclick.net/w/get.media?sid=12345&m=6&tp=8&d=s&c=1″ width=300 height=250 border=1></a></noscript>
snippet code as of April 7, 2010

A quick walk through the snippet code:

  • line 1 – Download the get.media script.
  • lines 2-3 – NOSCRIPT block in case JavaScript is not available.

Performance Analysis

This HTTP waterfall chart was generated by WebPagetest.org using IE 7 with a 1.5Mbps connection from Dulles, VA. It shows why this snippet has a significant impact on page load time even with just a few HTTP requests and a very small download size.

Keep in mind that valueclick-waterfall.png represents the actual content on the main page. Notice how that image is pushed back to item 6 in the waterfall chart. That’s because the get.media script (item 2) is downloaded using normal SCRIPT SRC tags. This blocks all subsequent HTTP requests in older browsers including IE 6&7. (Here we’re using IE7.)

In addition, the get.media script is served through a redirect in IE (but not in Firefox). For IE a total of three sequential HTTP requests must be completed before the ad is returned. The ad is inserted using document.write, which can further block the main content on the page. In this one page load, the main content (valueclick-waterfall.png) is blocked for 338 + 345 + 163 = 846 milliseconds.

In my analysis of ad snippets I focus only on the ad framework, not on the actual ads. The ValueClick ad framework is very light – just two redirects and one small script that does document.write. Therefore, there are only a few problem areas in which to look for performance improvements, but they’re big:

1. The redirects block the page.

In IE there are two redirects in front of the get.media script. This is two roundrips from the user’s browser to the ValueClick servers and back again. The fact that these redirects don’t occur in Firefox leads me to believe that there’s a workaround for IE. Given that over 50% of Internet traffic uses IE, removing these redirects would have a positive impact on a significant number of users.

2. The get.media script blocks the main content of the page from loading.

It would be better to load the script without blocking, similar to what BuySellAds.com does.

3. The ad is inserted using document.write.

Scripts that use document.write slow down the page because they can’t be loaded asynchronously. Inserting ads into a page without using document.write can be tricky. BuySellAds.com solves this problem by creating a DIV with the desired width and height to hold the ad, and then setting the DIV’s innerHTML.


Ad networks are an amazing piece of technology. Having so many different companies share such a variety of content across millions of web sites is a real accomplishment. Techniques like document.write and scripts that block have made this possible. But the Web has evolved since these techniques were considered acceptable.

It’s critical that ad providers adopt new web development patterns so they can hit that win-win-win of a fast user experience, publisher content that renders immediately, and ads that appear quickly to drive impressions and click throughs.

1 Comment

Google adds site speed to search ranking

April 9, 2010 7:46 am | 12 Comments

Today, Google announced that a site’s speed has been added as a signal to Google’s search ranking algorithm: Using site speed in web search ranking.

In March 2008, one month after I started working here, Google announced that site speed was being incorporated into Adwords Quality Score. When I wrote my blog post about that change to Adwords (Google fosters a faster Internet) I had no idea that this was the beginning of a long series of contributions from Google for creating a faster Web. Since that time Google has released:

Two years ago when I talked to people about the Adwords change, most people thought it was a good idea, but the most frequent response was, “Doesn’t this favor larger companies that care about performance?” In my experience, small companies that care about performance are able to make improvements much more quickly than large companies. Small companies are typically more agile and have less legacy code to worry about.

I’m excited to see web performance optimization become a competitive advantage, and look forward to helping web developers around the world make their sites even faster. Make sure to run Page Speed and YSlow to find the most important performance improvements. If you still have questions, feel free to contact me. I’ll be happy to analyze your web site and give you some tips.

As much as I’m excited about how Google’s announcement raises awareness about web performance optimization among companies and developers, I’m most excited about what this means for users. Faster web sites lead to a better user experience. And that’s what it’s all about.

12 Comments

Speed Doesn’t Matter

April 1, 2010 9:33 am | 16 Comments

[Note the date this blog post was written.]

Six years. Wasted.

That’s how I’m feeling after reading a recent performance-related article from WebSundries.com. The article reviews the results of various performance changes made to their web site, and the impact it had on their business. Although the improvements did in fact improve several metrics, at the end of the day they found it didn’t matter than much

The conclusions are a surprise, to say the least. But DeWitt Leighter, WebSundries’ VP of Engineering who authored the article, brought up several good points to support their interpretation of the data:

Focusing on improving the speed of our web site did have benefits. Conversions increased by 27%. Unique users nearly doubled (97% increase), and session length increased from 12 to 19 minutes. But the costs to achieve this were also significant, and in our interpretation outweighed the benefits.

Among the costs of improving their web site performance was the development effort. Later Leighter described how their developers were burdened by having to learn how to program, and worry about the impact of their changes. Leighter says this was an unsustainable cost with no end in sight. The result?

We’ve gone full circle. The entire front page is now a single image. We use a technique called image maps to take the user directly to the product they wish to purchase. We reduced the size of our HTML document from nearly 17 kB to just under 200 bytes, and that’s before compression.

In response to the increases in conversions and users, DeWitt cited the fact that revenue was not one of their key business metrics, and user satisfaction was hard to measure and thus easy to ignore. When asked about the impact of downloading their 26 MB image map on mobile devices, Leighter recalled recent studies that show bandwidth is increasing and mobile usage is on the decline. As evidence he cited the fact that their web logs show almost no mobile users.

Chilling results to say the least. I’ll be watching closely to see how this alternative approach to web site performance plays out for WebSundries.com.

About WebSundries.com

WebSundries.com has been the destination for shoppers for nearly two centuries. Starting in 1826 as Leighter’s Dry Goods and Mercantile, they’re perhaps best known for their heavy investment in vacuum tubes and rotary phones, for which they still hold the world’s largest inventories. Rebranded as WebSundries.com in 1998, the company boasts having one of the most consistent customer bases on the Internet, with almost no change since they first launched their site.

16 Comments