mobile & desktop cache 2.0: how to create a scriptable cache
DESCRIPTION
In this webinar, we’ll describe how you can build your own Scriptable Cache based on HTML5 localStorage, making Mobile cache work and giving Desktop Cache a boost. We’ll discuss the value of a Scriptable Cache, show the key elements you’ll need to create, and mention some of the pitfalls you need to beware of.TRANSCRIPT
DIY Scriptable Cache Guy Podjarny, CTO [email protected] twi;er: @guypod
Agenda
• Caching 101 • Mobile & Desktop Scriptable Cache
– Concept – 6 Steps to Building a Scriptable Cache – Advanced Optimizations
• Q&A
2
The Value of a Scriptable Cache
• A dedicated cache, not affected by other sites • A robust cache, not cleared by power cycles • Better file consolidation
– Works in more cases – Cache Friendly – Less requests without more bytes
• Enable Advanced Optimizations – Robust Prefetching, Async CSS/JS…
• The Secret to Eternal Youth
3
Not For The Faint of Heart!
• DIY Scriptable Cache isn’t simple – No magic 3 lines of code
• Requires HTML & Resource modifications – Some of each
• The code samples are pseudo-code – They don’t cover all edge cases – They’re not optimized – They probably have syntax errors
4
Caching 101
What is a Cache?
• Storage of previously seen data
• Reduces costs • Accelerates results
• Sample savings: – Computation costs (avoid regenerating
content) – Network costs (avoid retransmitting content)
6
Cache Types
7
Browser -‐ Eliminates network Hme -‐ Shared by one user
Gateway -‐ Server resources from the faster intranet -‐ Shared per organizaHon
CDN Edge -‐ reduces roundtrip Hme – latency -‐ Shared by all users
Server-‐Side -‐ Reduces server load -‐ Faster turnaround for response -‐ Shared by all users
Caching - Expiry
• Cache Expiry Controlled by Headers – HTTP/1.0: Expires – HTTP/1.1: Cache-Control
• ETAG/Last-Modified Enables Conditional GET – Fetch Resource “If-Modified-Since”
• CDN/Server Cache can be manually purged
8
Stale Cache
• Outdated data in cache – Affects Browser Cache the most
• Versioning – Add a version number to the filename – Change the version when the file changes – Unique filename = long caching – stale cache
9
file.v1.js
var today = “11/10/26” var today = “11/10/27”
file.v2.js
Cache Sizes - Desktop • Ranges from 75MB to 250MB • Fits about 90-300 pages
– Average desktop page size is ~800 KB • Cycles fully every 1-4 days
– Average user browses 88 pages/day
10
Cache Sizes - Mobile • Ranges from 0 MB to 25MB • Fits about 0-60 pages (Average size ~400KB) • Memory Cache a bit bigger, but volatile
11
Conclusion
• Caching is useful and important • Cache sizes are too small
– Especially on Mobile • Cache hasn’t evolved with the times
– Stopped evolving with HTTP/1.1 in 2004 • Browser Cache evolved least of all
– Browsers adding smart eviction only now – Still no script interfaces for smart
caching
12
Scriptable Cache
Scriptable Browser Cache - Concept
• A cache accessible via JavaScript – Get/Put/Delete Actions
• What is it good for? – Cache parts of a page/resource – Adapt to cache state – Load resources in different ways
• Why don’t browsers support it today? – Most likely never saw the need – Useful only for advanced websites – Not due to security concerns (at least not good
ones)
14
Intro to HTML5 localStorage
• Dedicated Client-Side Storage – HTML5 standard – Replaces hacky past solutions
• Primarily used for logical data – Game high-score, webmail drafts…
• Usually limited to 5 MB • Enables simple get/put/remove commands • Supported by all modern browsers
– Desktop: IE8+, Firefox, Safari, Chrome, Opera – BB 6.0+, most others (http://mobilehtml5.org/)
15
Step 0: Utilities
16
var sCache = { … // Short name for localStorage db: localStorage, // Method for fetching an URL in sync getUrlSync: funcHon (url) { var xhr = new XMLH;pRequest(); xhr.open( ‘GET’, url, false); xhr.send(null); if (xhr.status==200) {
return xhr.responseText; } else {
return null; } } …}
Step 1: Store & Load Resources
17
var sCache = { … // Method for running an external script runExtScript: funcHon (url) { // Check if the data is in localStorage var data = db.getItem(url); if (!data) {
// If not, fetch it data = getUrlSync(url);
// Store it for later use db.setItem(url, data);
} // Run the script dynamically addScriptElement(data); } …}
Step 2: Recover on error
18
var sCache = { … runExtScript: funcHon (url) { // Check if the data is in localStorage var data = db && db.getItem(url); if (!data) {
// If not, fetch it data = $.get(url); // Store it for later use try { db.setItem(url, data) } catch(e) { }
} // Run the script dynamically addScriptElement(data); } …}
Step 3: LRU Cache – Cache State
19
var sCache = { … // Meta-‐Data about the cache capacity and state dat: {size: 0, capacity: 2*1024*1024, items: {} }, // Load the cache state and items from localStorage load: funcHon() { var str = db && db.getItem(“cacheData”); if (data) { dat = JSON.parse(x); } }, // Persist an updated state to localStorage save: funcHon() { var str = JSON.stringify(dat); try {db.setItem(“cacheData”, str); } catch(e) { } }, … }
Step 3: LRU Cache – Storing items
20
var sCache = { … storeItem: funcHon(name, data) { // Do nothing if the single item is greater than our capacity if (data.length > dat.capacity) return; // Make room for the object while(dat.items.length && (dat.size + data.length) > dat.capacity) { var elem = dat.pop(); // Remove the least recently used element try { db.removeItem(elem.name); } catch(e) { } dat.size -‐= elem.size; } // Store the new element in localStorage and the cache try {
db.setItem(name, data); dat.size += data.length; dat.items.push ({name: name, size: data.length});
} catch(e) { } } …
Step 3: LRU Cache – Getting items
21
var sCache = { … getItem: funcHon(name) { // Try to get the item var data = db && db.getItem(name); if (!data) return null; // Move the element to the top of the array, marking it as used for(var i=0;i<dat.items.length;i++) {
if (dat.items[i].name === name) { dat.items.unshiw(dat.items.splice(i,-‐1)); break; }
} return data; } …}
Post Step 3: Revised Run Script
22
var sCache = { … runExtScript: funcHon (url) { // Check if the data is in the cache var data = getItem(url); if (!data) {
// If not, fetch it data = $.get(url); // Store it for later use storeItem(url, data);
} // Run the script addScriptElement(data); } …}
Step 4: Versioning
23
// Today: File version 1 sCache.load(); sCache.runExtScript(‘res.v1.js’); sCache.save(); // Tomorrow: File version 2 sCache.load(); sCache.runExtScript(‘res.v2.js’); sCache.save(); // Old files will implicitly be pushed out of the cache // Also work with versioning using signature on content
What Have We Created So Far? • Scriptable LRU Cache
– Enforces size limits – Recovers from errors
• Dedicated Cache – Not affected by browsing other sites
• Robust Cache – Not affected by Mobile Cache Sizes – Survives Power Cycle and Process Reset
• Still Has Limitations: – Only works on same domain – Resources fetched sequentially
24
Step 5: Cross-Domain Resources
• Why Cross Domain? – Enables Domain Sharding – Various Architecture Reasons
• Solution: Self-Registering Scripts – Scripts load themselves into the cache – Added to the page as standard scripts – Note that one URL stores data as another URL
25
h;p://1.foo.com/res.v1.js
alert(1); sCache.storeItem( ‘h;p://1.foo.com/res.v1.js’, ’alert(1)’);
h;p://1.foo.com/store.res.v1.js
Step 6: Fetching Resources In Parallel
26
<script>sCache.load()</script> <script> // Resources downloaded in parallel doc.write(“<scr”+”ipt src=‘h;p://foo.com/store.foo.v1.js’></scr”+”ipt>”); doc.write(“<scr”+”ipt src=‘h;p://bar.com/store.bar.v1.js’></scr”+”ipt>”); </script> <!-‐-‐ Scripts won’t run unHl previous ones complete, and data is cached -‐-‐> <script>sCache.runExtScript(‘h;p://foo.com/foo.v1.js’); </script> <script>sCache.runExtScript(‘h;p://bar.com/bar.v1.js’); </script> <!-‐-‐ Note the different URLs! -‐-‐> <script>sCache.save();</script>
Step 6: Parallel Resources + Cache Check
27
var sCache = { … loadResourceViaWrite: funcHon (path, file) { // Check if the data is in the cache var data = getItem(url); if (!data) {
// If not, doc-‐write the store URL doc.write(“<scr”+”ipt src=‘” + path + “store.” + file + // Add the “store.” prefix “’></scr”+”ipt>”);
} } …}
Step 6: Parallel Downloads, with Cache
28
<script>sCache.load()</script> <script> // Resources downloaded in parallel, only if needed sCache.loadResourceViaWrite("h;p://foo.com/”,”foo.v1.js”); sCache.loadResourceViaWrite("h;p://bar.com/”,”bar.v1.js”); </script> <!-‐-‐ Scripts won’t run unHl previous ones complete, and data is cached -‐-‐> <script>sCache.runExtScript(‘h;p://foo.com/foo.v1.js’); </script> <script>sCache.runExtScript(‘h;p://bar.com/bar.v1.js’); </script> <!-‐-‐ Note the different URLs! -‐-‐> <script>sCache.save();</script>
What Have We Created?
• Scriptable LRU Cache – Enforces size limits – Recovers from errors
• Dedicated Cache – Not affected by browsing other sites
• Robust Cache – Not affected by Mobile Cache Sizes – Survives Power Cycle and Process Reset
• Works across domains • Resources downloaded in parallel
29
Understanding localStorage Quota
• Many browsers use UTF-16 for characters – Effectively halves the storage space – Safest to limit capacity to 2 MB
• Best value: Cache CSS & JavaScript – Biggest byte-for-byte impact on page load – Lowest variation allows for longest caching – Images are borderline too big for capacity
• Remember: Quotas are per top-level-domain – *.foo.com share the same quota
30
Advanced Optimizations
Adaptive Consolidation
• Fetch Several Resources with One Request – Store them as Fragments
• Adapt to Browser Cache State – If resources aren’t in cache, fetch them as one file – If some resources are in cache, fetch separate files – Optionally consolidate missing pieces
32
h;p://1.foo.com/foo.v1.js
alert(1); sCache.storeItem(‘/foo.v1.js’, ’alert(1)’); sCache.storeItem(‘/bar.v1.js’, ’alert(2)’);
h;p://1.foo.com/store.res.v1.js
h;p://1.foo.com/bar.v1.js
alert(2);
Adaptive vs. Simple Consolidation - #2
• User browsers Page A, then Page B – Assume each JS file is 20KB in Size
33
Page A
<script src=“a.js”></script> <script src=“b.js”></script> <script src=“c.js”></script>
Page B
<script src=“a.js”></script> <script src=“b.js”></script>
OpGmizaGon Total JS Requests Total JS Bytes
None 3 60KB
Simple ConsolidaHon 2 100KB
AdapHve ConsolidaHon 1 60KB
Adaptive vs. Simple Consolidation - #2
• User browsers Page A, then Page B – Assume each JS file is 20KB in Size
34
Page A
<script src=“a.js”></script> <script src=“b.js”></script> <script src=“c.js”></script>
Page B
<script src=“a.js”></script> <script src=“b.js”></script> <script src=“c.js”></script> <script src=“d.js”></script>
OpGmizaGon Total JS Requests Total JS Bytes
None 4 80KB
Simple ConsolidaHon 2 140KB
AdapHve ConsolidaHon 2 80KB
Adaptive vs. Simple Consolidation - #3
• External & Inline Scripts are often related • Breaks Simple Consolidation • Doesn’t break Adaptive Consolidation
35
Page: <script src=“a.js”></script> <script> var userType = “user”; If (mode==1) userType = “admin”; </script> <script src=“b.js”></script>
<script>sCache.runExtScript(‘a.js’)</script> <script> var userType = “user”; If (mode==1) userType = “admin”; </script> <script>sCache.runExtScript(‘b.js’)</script>
a.js var mode=1;
b.js alert(userType);
StoreAll.js sCache.storeItem(‘a.js’,’var mode=1;’) sCache.storeItem(‘b.js’,’alert(userType);’)
OpHmized Page:
Robust Prefetching
• In-Page Prefetching – Fetch CSS/JS Resources at top of page, to be
used later • Next-Page Prefetching
– Fetch resources for future pages • Robust and Predictable
– Not invalidated due to content type change in FF – Not invalided by cookies set in IE – Not reloaded when entering same URL in Safari – …
36
Async JS/CSS
• Async JS: Run scripts without blocking page – Doable without Scriptable Cache – Scriptable Cache allows script prefetching – Eliminates need to make fetch scripts block
• Async CSS: Download CSS without blocking – CSS ordinarily delay resource download & render – You can’t always know when a CSS file loaded – Scriptable Cache enables “onload” event – Can still block rendering if desired
37
Summary
• Caching is good – you should use it! • Scriptable Cache is better
– More robust – More reasonably sized on Mobile – Enables important optimizations
• The two aren’t mutually exclusive – “store” files should be cacheable – Images should likely keep using regular cache
38
Or… Use the Blaze Scriptable Cache! • Blaze automates Front-End Optimization
– No Software, Hardware or Code Changes needed – All the pitfalls and complexities taken care of
• Blaze optimizes Mobile & Desktop Websites – Applying the right optimizations for each client
See how much faster Blaze can make your site with our Free Report: www.blaze.io Contact Us: [email protected]
39