Storager case study: Bing, Google

March 28, 2011 9:26 pm | 30 Comments

Storager

Last week I posted my mobile comparison of 11 top sites. One benefit of analyzing top websites is finding new best practices. In that survey I found that the mobile version of Bing used localStorage to reduce the size of their HTML document from ~200 kB to ~30 kB. This is a good best practice in general and makes even more sense on mobile devices where latencies are higher, caches are smaller, and localStorage is widely supported.

I wanted to further explore Bing’s use of localStorage for better performance. One impediment is that there’s no visibility into localStorage on a mobile device. So I created a new bookmarklet, Storager, and added it to the Mobile Perf uber bookmarklet. (In other words, just install Mobile Perf – it bundles Storager and other mobile bookmarklets.)

Storager lets you view, edit, clear, and save localStorage for any web page on any browser – including mobile. Viewing localStorage on a 320×480 screen isn’t ideal, so I did the obvious next step and integrated Storager with Jdrop. With these pieces in place I’m ready to analyze how Bing uses localStorage.

Bing localStorage

My investigation begins by loading Bing on my mobile device – after the usual redirects I end up at the URL http://m.bing.com/?mid=10006. Opening Storager from the Mobile Perf bookmarklet I see that localStorage has ~10 entries. Since I’m not sure when these were written to localStorage I clear localStorage (using Storager) and hit reload. Opening Storager again I see the same ~10 entries and save those to Jdrop. I show the truncated entries below. I made the results public so you can also view the Storager results in Jdrop.

BGINFO: {"PortraitLink":"http://www.bing.com/fd/hpk2/Legzira_EN-US262...
CApp.Home.FD66E1A3: #ContentBody{position:relative;overflow:hidden;height:100%;-w...
CUX.Keyframes.B8625FE...: @-webkit-keyframes scaleout{from{-webkit-transform:scale3d(1,...
CUX.Site.18BDD936: *{margin:0;padding:0}table{border-collapse:separate;border-sp...
CUX.SiteLowRes.C8A1DA...: .blogoN{background-image:url(data:image/png;base64,iVBORw0KGg...
JApp.Home.DE384EBF: (function(){function a(){Type.registerNamespace("SS");SS.Home...
JUX.Compat.0907AAD4: function $(a){return document.getElementById(a)}var FireEvent...
JUX.FrameworkCore.A39...: (function(){function a(){Type.registerNamespace("BM");AjaxSta...
JUX.MsCorlib.172D90C3: window.ss={version:"0.6.1.0",isUndefined:function(a){return a...
JUX.PublicJson.540180...: if(!this.JSON)this.JSON={};(function(){function c(a){return a...
JUX.UXBaseControls.25...: (function(){function a(){Type.registerNamespace("UXControls")...
RMSM.Keys: CUX.Site.18BDD936~CUX.Keyframes.B8625FEE~CApp.Home.FD66E1A3~C...

These entries are written to localStorage as part of downloading the Bing search page. These entries add up to ~170 kB in size (uncompressed). This would explain the large size of the Bing HTML document on mobile. We can verify that these keys are downloaded via the HTML document by searching for a unique string from the data such as “FD66E1A3”. We find this string in the Bing document source (saved in Jdrop) as the id of a STYLE block:

<style data-rms="done" id="CApp.Home.FD66E1A3" rel="stylesheet" type="text/css">
#ContentBody{position:relative;overflow:hidden;height:100%;-webkit-tap-highlight-color:...

Notice how the content of this STYLE block matches the data in localStorage. The other localStorage entries also correspond to SCRIPT and STYLE blocks in the initial HTML document. Bing writes these blocks to localStorage and then on subsequent page views reads them back and inserts them into the document resulting in a much smaller HTML document download size. The Bing server knows which blocks are in the client’s localStorage via a cookie, where the cookie is comprised of the localStorage keys delimited by “~”:

RMSM=JApp.Home.DE384EBF~JUX.UXBaseControls.252CB7BF~JUX.FrameworkCore.A39F6425~
JUX.PublicJson.540180A4~JUX.Compat.0907AAD4~JUX.MsCorlib.172D90C3~CUX.SiteLowRes.C8A1DA4E~
CApp.Home.FD66E1A3~CUX.Keyframes.B8625FEE~CUX.Site.18BDD936~;

Just to be clear, everything above happens during the loading of the blank Bing search page. Once a query is issued the search results page downloads more keys (~95 kB additional data) and expands the cookie with the new key names.

Google localStorage

Another surprise from last week’s survey was that the mobile version of Google Search had 68 images in the results HTML document as data: URIs, compared to only 10 for desktop and iPad. Mobile browsers open fewer TCP connections and these connections are typically slower compared to desktop, so reducing the number of HTTP requests is important.

The additional size from inlining data: URIs doesn’t account for the large size of the Google Search results page, so perhaps localStorage is being seeded here, too. Using Storager we see over 130 entries in localStorage after a search for flowers. Here’s a sample. (As before, the key names and values may be truncated.)

 mres.-8Y5Dw_nSfQztyYx: <style>a{color:#11c}a:visited{color:#551a8b}body{margin:0;pad...
 mres.-Kx7q38gfNkQMtpx: <script> //<![CDATA[ var Zn={},bo=function(a,b){b&&Zn[b]||(ne...
 mres.0kH3gDiUpLA5DKWN: <style>.zl9fhd{padding:5px 0 0}.sc59bg{clear:both}.pyp56b{tex...
 mres.0thHLIQNAKnhcwR4: <style>.fdwkxt{width:49px;height:9px;background:url("data:ima...
 mres.36ZFOahhhEK4t3WE: <script> //<![CDATA[ var kk,U,lk;(function(){var a={};U=funct...
 mres.3lEpts5kTxnI2I5S: <script> //<![CDATA[ var Ec,Fc,Gc=function(a){this.Jl=a},Hc="...
 mres.4fbdvu9mdAaBINjE: <script> //<![CDATA[ u("_clOnSbt",function(){var a=document.g...
 mres.5QIb-AahnDgEGlYP: <script> //<![CDATA[ var cb=function(a){this.Cc=a},db=/\s*;\s...
 mres:time.-8Y5Dw_nSfQ...: 1301368541872
 mres:time.-Kx7q38gfNk...: 1301368542755
 mres:time.0kH3gDiUpLA...: 1301368542257
 mres:time.0thHLIQNAKn...: 1301368542223
 mres:time.36ZFOahhhEK...: 1301368542635
 mres:time.3lEpts5kTxn...: 1301368542579
 mres:time.4fbdvu9mdAa...: 1301368542720
 mres:time.5QIb-AahnDg...: 1301368542856

Searching the search results docsource for a unique key such as “8Y5D” we find:

<style id="r:-8Y5Dw_nSfQztyYx" type="text/css">
a{color:#11c}a:visited{color:#551a8b}body{margin:0;padding:0}...

Again we see that multiple SCRIPT and STYLE blocks are being saved to localStorage totaling 154 kB. On subsequent searches the HTML document size drops from the initial size of 220 kB uncompressed (74 kB compressed) to 67 kB uncompressed (16 kB compressed). In addition to the key names being saved in a cookie, it appears that an epoch time (in milliseconds) is associated with each key.

Conclusion

Bing and Google Search make extensive use of localStorage for stashing SCRIPT and STYLE blocks that are used on subsequent page views. None of the other top sites from my previous post use localStorage in this way. Are Bing and Google Search onto something? Yes, definitely. As I pointed out in my previous post, this is another example of a performance best practice that is used on a top mobile site but is not in the recommendations from Page Speed or YSlow. Many of the performance best practices that I’ve evangelized over the last six years for desktop apply to mobile, but I believe there are specific mobile best practices that we’re just beginning to identify. I’ve started using “High Performance Mobile” as the title of future presentations. Another book? hmmm….

30 Responses to Storager case study: Bing, Google

  1. “High Performance Mobile”? Go Steve!

    It will be a trilogy :)

  2. I wonder how this methodology compares, performance wise, with using the “manifest.cache” – as you would if you were building your site for offline browsing?

  3. Hey Steve, awesome and very useful findings!
    The more general strategy here is to use a “local storage API” for caching. It may be localStorage, the HTML5 SQL database or IndexedDB. One should be careful with just using localStorage. Right now it works really well in the most common mobile browsers but this might change soon. LocalStorage is extremely slow in multi process browsers because its implementation requires a synchronous IPC call to the mother process (Chrome is about 10000 times slower for reads and writes compared to classic WebKit. This can lead to significant slowness when accessing localStorage e.g. during animations). With Firefox Mobile the first multi process mobile browser appeared and it is likely that this trend will continue.

  4. What makes using localstorage preferable to the usual setting of expiry dates mechanism? Is it that by using localstorage you take the resources out of contention for a potentially limited amount of space in the caches or is there some other reason?

  5. Steve,

    Bing or Google doesn’t reduce the page size by using local storage. On the first request all the data has to be sent. The issue then becomes “one of freshness”. I agree that some elements (title etc) don’t change so can be pulled from local cache/storage. So you save on the next request.

    The bigger problem as Malte pointed out is the multi-process browser, because then it (local storage) has to be synched up. And that’s a lot of IPC which is really, really tricky (and really slow) under Android.

    Also something else to remember here… multiple windows. Try your same test again with 4 multiple windows and see how the timing/downloads change.

  6. @Jonathan: I think the app cache API is bad especially the way new updates aren’t seen until the next time the user restarts your app. localStorage doesn’t suffer from that problem.

    @Malte: I’ve heard other people caution about I/O delays using localStorage. Please comment back with URLs to data that you have about the 10000 times slower stat.

    @Alex: Browser caches don’t work as good as they should (see Call to improve browser caching) and are even worse on mobile.

  7. Steve: See http://jsperf.com/localstorage-read-write-test (see the browserscope results, especially for larger payloads). Basically on Safari/Classic WebKit access to localStorage is access to a hash table while the multi process browsers have to do a synchronous IPC call that is obviously several orders of magnitude slower (basically the worst possible action on a modern computer besides hitting disk or network).

    This might be not so much of a problem in load scenarios where the network is hit in parallel but it really hurts at runtime. Basically this code
    setInterval(function() {
    left += 10;
    node.style.left = left + ‘px’;
    localStorage.setItem(‘animationState’, left);
    }, 1000 / 30);
    results in a smooth animation in webkit browsers like the iOS or Android browser but leads to a jagged animation in Chrome.

  8. Steve,

    I’m curious why the techniques with LocalStorage are used for the mobile versions of the search sites but not for the standard desktop versions – I would think that this technique would work well all clients, no?

    thanks

    -simon

  9. @Simon: 1. Caching resources in localStorage is more work than relying on the browser cache for HTTP responses. 2. It doesn’t work for CDNs. 3. A larger percentage of desktop browsers don’t support localStorage compared to mobile. 4. It’s not as critical because latency is lower and connection speeds are higher on desktop than mobile. I’m not saying it shouldn’t be done – I’m just answering your question of why we see it on mobile rather than desktop.

  10. @Steve

    Why localStorage does not work for CDNs?

  11. @ds: Most likely the domains would be different between a CDN and main page, and then the main page wouldn’t be able to manipulate the CDN’s localStorage. But I haven’t tested this.

  12. @Steve

    Thanks

    @Malte

    Notice that mobile phone’s localStorage is much faster than browser, is that because Chrome’s implementation is use IPC?

    Why IE is much much faster? Any insights?

  13. There’s a good explanation of this technique at mix11 :
    http://channel9.msdn.com/Events/MIX/MIX11/RES04
    I know i saw this link on twitter first, maybe @souders… can’t remember

  14. @Joey: I was going to tweet that video, but it uses document.write, synchronous XHR, and loads scripts using HTML tags in a blocking fashion. As he mentions, this is interesting research but I don’t think it’s ready for daily use.

  15. Odd that there’s CDATA tags in there, what would the benefit be of including them?

  16. @Jethro: CDATA sections are often used when including JS in an HTML response: http://en.wikipedia.org/wiki/CDATA#Use_of_CDATA_in_program_output

  17. @Steve Yeah, I know they’re important for making valid xhtml, but that seems like a waste of bits in the mobile world.

  18. @Steve, can you clarify the fail-safe approach for this technique? That is, what if localStorage is compromised or lost while the cookie exists?

  19. @Morgan: You can see the answer in the Google Search JS code:
    var c=localStorage.getItem(“mres.”+a);
    c ?
    (document.write(c),
    localStorage.setItem(“mres:time.”+a,Date.now()))
    :
    (window._clearCookie(“MRES”),
    document.location.reload(!0))

    If the block is NOT found in localStorage, the cookie is cleared and the page is reloaded. When the search results are reloaded the server does NOT see the cookie, so it returns the entire payload and repopulates localStorage.

  20. @Steve, thanks for your clarification.
    I thought it could download external CSS & JS in this “compromised” situation. But clearing cookie and reloading is clearly better and simpler strategy.

  21. Another book? Yes please! I was just talking with fellow developers today about how we can improve the performance of our fledgling web app. From a mobile device, it’s so hard to diagnose where the problem lies — downloading html and resource, evaluating and executing javascript, getting data? I just discovered your Mobile Perf bookmarklet today, and I can’t wait to dig in! Thanks for all the great tools and analysis.

  22. Hi Steve, I just read an article about “localStorage Read Performance”(http://calendar.perfplanet.com/2011/localstorage-read-performance/) by Nicholas, he said that reading from LocalStroage was slower than reading from object, so does it make sense for storing data in localstorage insead of requesting from far-future cached file?

    Thanks,

    -Adam Lu

  23. @Adam: Nicholas’ article is NOT a comparison to a cached file. Please read Nicholas’ comment where he says he “was going to do a comparison to HTTP requests and just ran out of time.” His article compares localStorage to accessing data in a JavaScript variable. I’m not surprised access a variable is faster, but variables obviously don’t persist across pages & sessions.

  24. Gidday Steve

    Just wondering how secure localStorage is? I’m looking at using it to store js code, but if a user has the ability to change the code in the localStorage cache, that would be a security concern if you are injecting the code back from the cache into the webpage.

    So the question is, can a user change the code in the cache?

  25. Shaun: It is an issue if 3rd parties have access to your website. Here’s a thread from the W3C web crypto list.

  26. Ahh. I have a friend who is into web security, so I’ll go over that with him.

    I’m trying to save css to localStorage now, and have all the code in place, except I can’t figure how to load the external css into a string to go into a variable to be stored in cache. Any chance of you showing an example of that part?

  27. Please disregard my second to last post.

    Regarding security again – Storager has the ability to edit localStorage, right? And you’ve been able to view Bing and Google localStorage, so I’m assuming they are unencrypted. So what stops you or anyone altering the js that they have saved, and it wreaking havoc on their webpages when i’s loaded back out of localStorage?

  28. Shaun: You could look at Gmail or Bing to see how they do CSS from localStorage. Please read the W3C web crypto thread for more insight on the security issues.

  29. Cheers Steve. I’ve got my site to check for css in localstorage, and either load it, download the new version if I’ve added a different expiry time, or load CSS the normal way if localStorage isn’t supported. Works well and I’ve seen things speed up on my site. The main bottleneck now is the browser contacting the server, but unless I use CDN, I don’t thing there is any way to make that faster (testing on Pingdom show New York connects about 5-10x faster than their Amsterdam browser).

    As far as the W3C thread goes, from what I gather there is no API yet for protecting data in localStorage etc, but they are working on the plan for one. Is that correct?

  30. Shaun: Crypto API is not built into browsers, but you should be able to find a JS lib that isn’t too big (< 10K).