Diffable: only download the deltas
There were many new products and projects announced at Velocity, but one that I just found out about is Diffable. It’s ironic that I missed this one given that it happened at Velocity and is from Google. The announcement was made during a whiteboard talk, so it didn’t get much attention. If your web site has large JavaScript downloads you’ll want to learn more about this performance optimization technique.
The Diffable open source project has plenty of information, including the Diffable slides used by Josh Harrison and James deBoer at Velocity. As explained in the slides, Diffable uses differential compression to reduce the size of JavaScript downloads. It makes a lot of sense. Suppose your web site has a large external script. When a new release comes out, it’s often the case that a bulk of that large script is unchanged. And yet, users have to download the entire new script even if the old script is still cached.
Josh and James work on Google Maps which has a main script that is ~300K. A typical revision for this 300K script produces patches that are less than 20K. It’s wasteful to download that other 280K if the user has the old revision in their cache. That’s the inspiration for Diffable.
Diffable is implemented on the server and the client. The server component records revision deltas so it can return a patch to bring older versions up to date. The client component (written in JavaScript) detects if an older version is cached and if necessary requests the patch to the current version. The client component knows how to merge the patch with the cached version and evals the result.
The savings are significant. Using Diffable has reduced page load times in Google Maps by more than 1200 milliseconds (~25%). Note that this benefit only affects users that have an older version of the script in cache. For Google Maps that’s 20-25% of users.
In this post I’ve used scripts as the example, but Diffable works with other resources including stylesheets and HTML. The biggest benefit is with scripts because of their notorious blocking behavior. The Diffable slides contain more information including how JSON is used as the delta format, stats that show there’s no performance hit for using eval, and how Diffable also causes the page to be enabled sooner due to faster JavaScript execution. Give it a look.
Annie Sullivan | 09-Jul-10 at 9:50 am | Permalink |
I love the diffable slides! What a great example of how to measure the effect of a performance optimization.
Kroc Camen | 09-Jul-10 at 9:55 am | Permalink |
I see two drawbacks:
1. Patching v1 to v2 doesn’t then allow the browser to cache v2, right? (It’s just eval).
2. Since the eval isn’t cached by the browser, the v1->v2 patch has to be requested every time they visit the site (until cache is invalidated) rather than have v2 cached.
What’s your thoughts on these issues?
Joshua Harrison | 09-Jul-10 at 9:59 am | Permalink |
Kroc,
You are correct in noticing that the patched version is not cached. However, the v1->v2 patch itself IS cached, meaning that the next time the user visits, if there has not been a new release, then both v1 and the v1->v2 patch are retrieved from cache. Also, we are working on incorporating local storage, which would allow the updated version to be persisted each time it is patched.
Matt Mastracci | 09-Jul-10 at 1:24 pm | Permalink |
How does it detect whether the script is cached? Or is that another way of saying that it executes what’s in the cache and that cached script does some additional magic to test whether it’s up-to-date?
Matt Mastracci | 09-Jul-10 at 1:32 pm | Permalink |
I read the slides again and poked around in the source. I think I understand now. The HTML page itself declares which version of the script to execute. If the strongly-cached version of the script isn’t up-to-date based on that version information provided by the HTML page, it forces a fetch of the diff.
Very cool!
http://code.google.com/p/diffable/source/browse/trunk/j2ee-diffable/src/main/resources/com/google/diffable/scripts/JsDictionaryBootstrap.js
I could see this being useful for serving CSS as well. Aggregrate all of your resources, CSS with inlined url(data:) resources and scripts together in a single bundle and generate diffs on that aggregate content.
Joshua Harrison | 09-Jul-10 at 1:37 pm | Permalink |
Matt,
You are correct in the second part of your comment. Diffable resources are turned into strings and embedded within some bootstrap code to produce the actual script, which we shall call ‘final.js’ for this explanation. The ‘final.js’ script contains the original content (as a string), the version of the original content, and some code that checks the version against a version in the container page, requesting a diff and patching when needed. If the version of the resource contained in the HTML page does not match the version contained inside ‘final.js,’ then ‘final.js’ knows it is being served from cache.
Matt Mastracci | 09-Jul-10 at 1:54 pm | Permalink |
Joshua,
Thanks again for presenting this interesting idea. I think it might be possible to manage this in the context of static-only CDN (I’m thinking Amazon Cloudfront or AppEngine’s built-in CDN) via offline pre-computation of diffs and careful management of page expiry.
Joshua Harrison | 09-Jul-10 at 2:10 pm | Permalink |
Matt,
I think you are absolutely right, and hopefully all of these ideas can be incorporated into the project. I am currently working on bootstraps for CSS and HTML content. I am also working on making the bootstrap code aware of other resources in the current page, allowing multiple resources in a page to be patched with a single diff request. Lastly, getting a working apache module is a top priority. I would love to be able to stick an apache server in front of the content server and have it do all the modifications automatically.
Thanks a lot for your interest! All contributions would be greatly appreciated. :)
Sean Hogan | 09-Jul-10 at 4:11 pm | Permalink |
Wouldn’t it be better to use whatever is in the cache and load (but don’t use in the current session) the freshest version in the background? Then the freshest version is available in the next session.
Joshua Harrison | 09-Jul-10 at 5:42 pm | Permalink |
Sean,
The main problem with this approach occurs when there are dependencies between your client and server. If this is the case, then everyone must get the latest version of the client when the new version of the server is pushed. Otherwise, having two possible client versions in the wild would mean you would need to run both versions of the server, and direct between the two. If this issue is not a concern, then you could definitely take the approach you describe, and in fact this is the approach HTML5 AppCache uses.
Sean Hogan | 09-Jul-10 at 6:40 pm | Permalink |
@Joshua,
Thanks for the response.
Andrew N Marshall | 10-Jul-10 at 9:32 am | Permalink |
Interesting idea, but…
Returning to Kroc’s point, the “expires never” on the specific versions will lead a commonly used site to always base on the first whole version downloaded.
Extending the example, when version 3 is pushed to the server, the client will find v1 in cache and request v1->v3. When version 10 comes out, until the cache entry for v1 is expunged, the client will again request relative to v1, that being v1->v10.
Of course, writable local storage will fix this.
Reminds me of Marimba’s Castanet way way back in the day.
(Spam blocker: “3 times tin =”? )
Joshua Harrison | 10-Jul-10 at 7:23 pm | Permalink |
Andrew,
We have come up with two wails to deal with the issue you raise. The first, easier way is to change the URL, and this is what we currently do. When the deltas become so large as to offset the benefit of patching, we simply start over by changing the script URL. The second way, which we haven’t actually gotten a chance to play with yet, is to set a valid expires time on the base scripts. If the expires time is, say, 2 weeks, then you would hypothetically only ever need to keep around 2 weeks worth of deltas.
Sergey Chernyshev | 12-Jul-10 at 3:20 pm | Permalink |
Great to see this actually done! I had this idea for a while now and never had a chance to implement it ;)
I think this can be bundled with some SVN/git code to make it easier for people to implement. It can also help with predicting proper expiration window based on historical data for example.
Joshua Harrison | 14-Jul-10 at 10:50 am | Permalink |
So I have seen several complaints about the scale on the Y-axis of the performance graph which Steve included from my slides. These concerns are completely valid, and the graph has been modified:
http://googlediffable.blogspot.com/
Thanks for the feedback and sorry for the graph and the delay in fixing it!