• Like
How Edmunds Got in the Fast Lane: 80% Reduction in Page Load Time in 3 Simple Steps
Upcoming SlideShare
Loading in...5
×

How Edmunds Got in the Fast Lane: 80% Reduction in Page Load Time in 3 Simple Steps

  • 144,630 views
Uploaded on

(YouTube presentation is at the end of the slides) …

(YouTube presentation is at the end of the slides)

Back in the day, the onLoad event on our edmunds.com and insidelien.com pages used to take 9 seconds to fire! Yeah, we thought it was awful too. That's why in late 2008, we set out to do something about. In this deck, I will discuss the three main concepts that helped us take our pages from slow to really fast.

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
  • Grate talk , is the Javascript loader code available for review? i'm curious how you guys handled the same resources being asked by more than one 'consumer' at the same time.
    thanks
    Are you sure you want to
    Your message goes here
  • This deck was presented in a Google Tech Talk that could be watched here http://www.youtube.com/watch?v=5_-YukDEDBE
    Are you sure you want to
    Your message goes here
  • Thanks, Jonathan. Feel free to ping us if you have any questions.
    Are you sure you want to
    Your message goes here
  • Awesome! Lots of good stuff for our team to use here. Thank you for sharing...
    Are you sure you want to
    Your message goes here
No Downloads

Views

Total Views
144,630
On Slideshare
0
From Embeds
0
Number of Embeds
4

Actions

Shares
Downloads
203
Comments
4
Likes
21

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. HOW EDMUNDS GOT IN THE FAST LANE 80% Reduction in Page Load Time in Simple Steps by Ismail ElshareefSunday, January 16, 2011
  • 2. The Results onLoad: 8.4s ➙ 1.9s (≈ 80% reduction) Page Views: 20% Bounce Rate: 4% Ad Impression Variance: 3%Sunday, January 16, 2011
  • 3. Edmunds, Inc. Online since 1995 Properties: 200M+ page views/month Revenue = Ads + LeadsSunday, January 16, 2011
  • 4. Memory LaneSunday, January 16, 2011
  • 5. Edmunds.com Legacy SiteSunday, January 16, 2011
  • 6. Meanwhile ... “95% of Performance is Frontend” - Steve Souders “Performance Matters!” - Organizations +100ms in response time ➡ -1% in sales -30% in file size ➡ +30% in requestsSunday, January 16, 2011
  • 7. The Real Picture!Sunday, January 16, 2011
  • 8. Site Condition - Too Many Requests (150+) - Many Blocking Requests (20+) - Perceived Slowness (onLoad in 8.4s) - No Caching - 1 Domains Serves All Requests - External Factors (ads, videos, ..etc) - Dependency on DOM EventsSunday, January 16, 2011
  • 9. We Started Tinkering ...Sunday, January 16, 2011
  • 10. Quick Wins: CACHING! - Solution Added Expires Header + Removed Etags - Result: 34% reduction in bandwidth = 34TB annual savings = FREE video streaming for 2 years = Faster pages when cache is primedSunday, January 16, 2011
  • 11. ... but Legacy was complicated.Sunday, January 16, 2011
  • 12. 2008: A Vision Was BornSunday, January 16, 2011
  • 13. Redesign Objectives PERFORMANCE (Faster Page Loads .. onLoad =< 1.5s) RICHER CONTENT (Flash, video, slicker UXD, ...etc) BETTER REVENUE (Positive impact on ad impressions)Sunday, January 16, 2011
  • 14. The Mindset FASTER PAGES POSITIVE USER EXPERIENCE HIGHER REVENUESunday, January 16, 2011
  • 15. The Challenge HTTP Requests Page Performance User ExperienceSunday, January 16, 2011
  • 16. Types of HTTP Requests 1) Our Own Requests (files served from our own domains) 2) 3rd-party Requests (files served from other domains)Sunday, January 16, 2011
  • 17. 3rd-party Requests Exist in Two Forms JavaScript iFrame Cons: Cons: - Access to DOM - Fixed width/height - document.write Pros: Pros: - Easy to lazy-load - Richer Content - Sandboxed CodeSunday, January 16, 2011
  • 18. 3rd-party Requests Roles on Our Sites JavaScript iFrame Analytics A/B Testing Ads Video Widgets Monitoring SurveysSunday, January 16, 2011
  • 19. Our Challenge 3rd-party Requests HTTP Requests Page Performance User ExperienceSunday, January 16, 2011
  • 20. 3rd-party iFrame 3rd-party JavaScript Agreeable ProblematicSunday, January 16, 2011
  • 21. Mission: Control 3rd Attempt #1: Override document.write()function Example: var buffer = []; document.write = function(st) { buffer.push(st); } // when a specific event occurs ... var s = buffer.join(‘’); document.getElementById(‘destination’).innerHTML = s; Problem: Didn’t work with daisy-chained document.write callsSunday, January 16, 2011
  • 22. Mission: Control 3rd Attempt #2: Load in iFrame and Copy on Load (iFrame’n’Copy) Example: var jsAd = “http://ad.doubleclick.net/adj/....”; iFrameObj.src = “http://www.edmunds.com/adSub.html?”+jsAd; // Option 1: listen to iFrame onLoad event // Option 2: iFrame page calls parent when done Problem: Buggy in IE 7 + Hard to MaintainSunday, January 16, 2011
  • 23. Our New Found Creed  You Can Not Control EverythingSunday, January 16, 2011
  • 24. Edmunds vs. 3rd-party Requests %"#$ %"#$ &()*+,$-./).+0+$ 12345206$-./).+0+$ !"#$ !"#$ New Sites Old Sites &()*+,$-./).+0+$ 12345206$-./).+0+$Sunday, January 16, 2011
  • 25. Our New Found Creed  Make What You Control FASTSunday, January 16, 2011
  • 26. JavaScript LoaderSunday, January 16, 2011
  • 27. How Does it Work?Sunday, January 16, 2011
  • 28. How Does it Work? ❶ Quick page is served to userSunday, January 16, 2011
  • 29. How Does it Work? 2 1 ❶ Quick page is 3 served to user 4 ❷ Page components register themselves with the page 5 6 7Sunday, January 16, 2011
  • 30. How Does it Work? 2 1 ❶ Quick page is 3 served to user 4 ❷ Page components register themselves with the page 5 ❸ Registered components rendered 6 in parallel 7Sunday, January 16, 2011
  • 31. Component 1 Component 2 Component 3 Registration Component 4 Component 5 Process Component 6 I need YUI’s Carousel module. Here’s my code, render me right away! I need flash.js and here’s my code. I need to be rendered right away Component 7 I need YUI’s Carousel module and here’s my code. I am below the fold so I can wait Component 8Sunday, January 16, 2011
  • 32. Component 1 Component 2 Component 3 Registration Component 4 Component 5 Process Declare Dependencies Component 6 I need YUI’s Carousel module. Here’s my code, render me right away! I need flash.js and here’s my code. I need to be rendered right away Component 7 I need YUI’s Carousel module and here’s my code. I am below the fold so I can wait Component 8Sunday, January 16, 2011
  • 33. Component 1 Component 2 Component 3 Registration Component 4 Component 5 Process Declare Dependencies Component 6 I need YUI’s Carousel module. Here’s my code, render me right away! Submit Functionality I need flash.js and here’s my code. I need to be rendered right away Component 7 I need YUI’s Carousel module and here’s my code. I am below the fold so I can wait Component 8Sunday, January 16, 2011
  • 34. Component 1 Component 2 Component 3 Registration Component 4 Component 5 Process Declare Dependencies Component 6 I need YUI’s Carousel module. Here’s my code, render me right away! Submit Functionality I need flash.js and here’s my code. I need to be rendered right away Component 7 Set Priority I need YUI’s Carousel module and here’s my code. I am below the fold so I can wait Component 8Sunday, January 16, 2011
  • 35. Registration Process Declare Dependencies PAGESETUP.files.push(file1.js); PAGESETUP.files.push(file2.js); PAGESETUP.files.push(file7.js); Submit functionality PAGESETUP.addControl(function() { // .... // Anything from rendering a component to making an AJAX call. All goes here. // ... }, high); Set PrioritySunday, January 16, 2011
  • 36. Process function load() { // ..... // ..... Dependencies var parent = arguments.callee; if (unique_files.length) { var file = unique_files.shift(); var js = document.createElement(script); js.type = text/javascript; js.src = file; if (!FF || FF >= 2) { js.onreadystatechange = function() { if (this.readyState == complete || this.readyState == loaded || this.status == 304 || this.status == 404) { parent(); } }; } else { parent(); } document.getElementsByTagName(head)[0].appendChild(js); if (PAGESETUP.execControls) { PAGESETUP.execControls(); } } }Sunday, January 16, 2011
  • 37. Get a reference to the function Process function load() { // ..... // ..... Dependencies var parent = arguments.callee; if (unique_files.length) { var file = unique_files.shift(); var js = document.createElement(script); js.type = text/javascript; js.src = file; if (!FF || FF >= 2) { js.onreadystatechange = function() { if (this.readyState == complete || this.readyState == loaded || this.status == 304 || this.status == 404) { parent(); } }; } else { parent(); } document.getElementsByTagName(head)[0].appendChild(js); if (PAGESETUP.execControls) { PAGESETUP.execControls(); } } }Sunday, January 16, 2011
  • 38. Get a reference to the function Process function load() { // ..... // ..... Dependencies var parent = arguments.callee; if (unique_files.length) { var file = unique_files.shift(); var js = document.createElement(script); Process unique js.type = text/javascript; js.src = file; dependencies if (!FF || FF >= 2) { js.onreadystatechange = function() { if (this.readyState == complete || this.readyState == loaded || this.status == 304 || this.status == 404) { parent(); } }; } else { parent(); } document.getElementsByTagName(head)[0].appendChild(js); if (PAGESETUP.execControls) { PAGESETUP.execControls(); } } }Sunday, January 16, 2011
  • 39. Get a reference to the function Process function load() { // ..... // ..... Dependencies var parent = arguments.callee; if (unique_files.length) { var file = unique_files.shift(); var js = document.createElement(script); Process unique js.type = text/javascript; js.src = file; dependencies if (!FF || FF >= 2) { js.onreadystatechange = function() { if (this.readyState == complete || For all browsers but this.readyState == loaded || FireFox < 4, Download this.status == 304 || this.status == 404) dependencies serially ... { parent(); } }; } else { parent(); } document.getElementsByTagName(head)[0].appendChild(js); if (PAGESETUP.execControls) { PAGESETUP.execControls(); } } }Sunday, January 16, 2011
  • 40. Get a reference to the function Process function load() { // ..... // ..... Dependencies var parent = arguments.callee; if (unique_files.length) { var file = unique_files.shift(); var js = document.createElement(script); Process unique js.type = text/javascript; js.src = file; dependencies if (!FF || FF >= 2) { js.onreadystatechange = function() { if (this.readyState == complete || For all browsers but this.readyState == loaded || FireFox < 4, Download this.status == 304 || this.status == 404) dependencies serially ... { parent(); } }; Otherwise, download in } else { parallel parent(); } document.getElementsByTagName(head)[0].appendChild(js); if (PAGESETUP.execControls) { PAGESETUP.execControls(); } } }Sunday, January 16, 2011
  • 41. Get a reference to the function Process function load() { // ..... // ..... Dependencies var parent = arguments.callee; if (unique_files.length) { var file = unique_files.shift(); var js = document.createElement(script); Process unique js.type = text/javascript; js.src = file; dependencies if (!FF || FF >= 2) { js.onreadystatechange = function() { if (this.readyState == complete || For all browsers but this.readyState == loaded || FireFox < 4, Download this.status == 304 || this.status == 404) dependencies serially ... { parent(); } }; Otherwise, download in } else { parallel parent(); } document.getElementsByTagName(head)[0].appendChild(js); if (PAGESETUP.execControls) { PAGESETUP.execControls(); } } When done downloading, } render registered components!Sunday, January 16, 2011
  • 42. Rendering Components execControls: function(start) { // Get a timestamp here to indicate the start of the process // Merge all the queues into one! high->normal->low var merged = this.merged; // Go through the merged array and execute the chunks! setTimeout(function() { // Get a chunk from the top of the array var item = merged.shift(); if(item){ // Execute the chunk! item.call(); } if (merged.length > 0) { // Wait 25 ms and then get the next chunk setTimeout(arguments.callee, 25); } else { // Otherwise, get a timestamp to indicate the end of the process } }, 0); },Sunday, January 16, 2011
  • 43. Rendering Components execControls: function(start) { // Get a timestamp here to indicate the start of the process // Merge all the queues into one! high->normal->low var merged = this.merged; // Go through the merged array and execute the chunks! setTimeout(function() { // Get a chunk from the top of the array Combine functions var item = merged.shift(); submitted by if(item){ components in // Execute the chunk! item.call(); priority order (high } -> normal -> low) if (merged.length > 0) { // Wait 25 ms and then get the next chunk setTimeout(arguments.callee, 25); } else { // Otherwise, get a timestamp to indicate the end of the process } }, 0); },Sunday, January 16, 2011
  • 44. Rendering Components execControls: function(start) { // Get a timestamp here to indicate the start of the process // Merge all the queues into one! high->normal->low var merged = this.merged; // Go through the merged array and execute the chunks! setTimeout(function() { // Get a chunk from the top of the array Combine functions var item = merged.shift(); submitted by if(item){ components in // Execute the chunk! Process one function at item.call(); a time ... priority order (high } -> normal -> low) if (merged.length > 0) { // Wait 25 ms and then get the next chunk setTimeout(arguments.callee, 25); } else { // Otherwise, get a timestamp to indicate the end of the process } }, 0); },Sunday, January 16, 2011
  • 45. Rendering Components execControls: function(start) { // Get a timestamp here to indicate the start of the process // Merge all the queues into one! high->normal->low var merged = this.merged; // Go through the merged array and execute the chunks! setTimeout(function() { // Get a chunk from the top of the array Combine functions var item = merged.shift(); submitted by if(item){ components in // Execute the chunk! Process one function at item.call(); a time ... priority order (high } -> normal -> low) if (merged.length > 0) { // Wait 25 ms and then get the next chunk setTimeout(arguments.callee, 25); } else { // Otherwise, get a timestamp to indicate the end of the process } every 25 ms! }, 0); },Sunday, January 16, 2011
  • 46. Our New Found Creed  Treat Everything Else as a Black BoxSunday, January 16, 2011
  • 47. 3rd-party iFrame 3rd-party JavaScript Agreeable ProblematicSunday, January 16, 2011
  • 48. 3rd-party iFrame 3rd-party JavaScript Agreeable ProblematicSunday, January 16, 2011
  • 49. 3rd-party Handling Logic Component Placeholder Markup on a page YES NO 3rd-party? Process through JS Loader JavaScript iFrame Render before </html> in a Register as "3rd- Remove original call hidden <div> party" component Relocate generated markup into placeholderSunday, January 16, 2011
  • 50. Rendering 3rd-party JavaScript: Part 1 - Register the Call // Add the placeholder <div id="js-ad1"></div> <script type="text/javascript"> (function() { // Construct the ad URL var ad = new EDMUNDS.AdUnit(); // Inform the PAGESETUP object of what’s going on PAGESETUP.thirdpartyids.push(js-ad1); PAGESETUP.thirdpartydetails[js-ad1] = {}; PAGESETUP.thirdpartydetails[js-ad1][type] = ad; PAGESETUP.thirdpartydetails[js-ad1][src] = ad.getAdUrl(); })(); </script>Sunday, January 16, 2011
  • 51. Rendering 3rd-party JavaScript: Part 1 - Register the Call Placeholder Markup // Add the placeholder <div id="js-ad1"></div> <script type="text/javascript"> (function() { // Construct the ad URL var ad = new EDMUNDS.AdUnit(); // Inform the PAGESETUP object of what’s going on PAGESETUP.thirdpartyids.push(js-ad1); PAGESETUP.thirdpartydetails[js-ad1] = {}; PAGESETUP.thirdpartydetails[js-ad1][type] = ad; PAGESETUP.thirdpartydetails[js-ad1][src] = ad.getAdUrl(); })(); </script>Sunday, January 16, 2011
  • 52. Rendering 3rd-party JavaScript: Part 1 - Register the Call Placeholder Markup // Add the placeholder <div id="js-ad1"></div> Register details about <script type="text/javascript"> the JavaScript Request (function() { // Construct the ad URL var ad = new EDMUNDS.AdUnit(); // Inform the PAGESETUP object of what’s going on PAGESETUP.thirdpartyids.push(js-ad1); PAGESETUP.thirdpartydetails[js-ad1] = {}; PAGESETUP.thirdpartydetails[js-ad1][type] = ad; PAGESETUP.thirdpartydetails[js-ad1][src] = ad.getAdUrl(); })(); </script>Sunday, January 16, 2011
  • 53. Rendering 3rd-party JavaScript: Part 1 - Register the Call Placeholder Markup // Add the placeholder <div id="js-ad1"></div> Register details about <script type="text/javascript"> the JavaScript Request (function() { // Construct the ad URL var ad = new EDMUNDS.AdUnit(); // Inform the PAGESETUP object of what’s going on Register: placeholder ID PAGESETUP.thirdpartyids.push(js-ad1); PAGESETUP.thirdpartydetails[js-ad1] = {}; PAGESETUP.thirdpartydetails[js-ad1][type] = ad; PAGESETUP.thirdpartydetails[js-ad1][src] = ad.getAdUrl(); })(); </script>Sunday, January 16, 2011
  • 54. Rendering 3rd-party JavaScript: Part 1 - Register the Call Placeholder Markup // Add the placeholder <div id="js-ad1"></div> Register details about <script type="text/javascript"> the JavaScript Request (function() { // Construct the ad URL var ad = new EDMUNDS.AdUnit(); // Inform the PAGESETUP object of what’s going on Register: placeholder ID PAGESETUP.thirdpartyids.push(js-ad1); PAGESETUP.thirdpartydetails[js-ad1] = {}; PAGESETUP.thirdpartydetails[js-ad1][type] = ad; PAGESETUP.thirdpartydetails[js-ad1][src] = ad.getAdUrl(); })(); </script> Register: JavaScript CallSunday, January 16, 2011
  • 55. Rendering 3rd-party JavaScript: Part 2 - Render at the bottom of the page <script type="text/javascript"> if (PAGESETUP.thirdpartyids.length > 0) { (function() { var id = PAGESETUP.thirdpartyids.shift(); var file = PAGESETUP.thirdpartydetails[id].src; if (file) { document.write(<div id="+id+-cache+" style="display:none;"> <div id="+id+-root+">); document.write(<script type="text/javascript" src="+file+" ></script>); } })(); } </script> <script type="text/javascript">document.write(</div></div>);</script>Sunday, January 16, 2011
  • 56. Rendering 3rd-party JavaScript: Part 2 - Render at the bottom of the page Get the registered ID <script type="text/javascript"> if (PAGESETUP.thirdpartyids.length > 0) { (function() { var id = PAGESETUP.thirdpartyids.shift(); var file = PAGESETUP.thirdpartydetails[id].src; if (file) { document.write(<div id="+id+-cache+" style="display:none;"> <div id="+id+-root+">); document.write(<script type="text/javascript" src="+file+" ></script>); } })(); } </script> <script type="text/javascript">document.write(</div></div>);</script>Sunday, January 16, 2011
  • 57. Rendering 3rd-party JavaScript: Part 2 - Render at the bottom of the page Get the registered ID <script type="text/javascript"> Get the registered if (PAGESETUP.thirdpartyids.length > 0) { (function() { JavaScript Call var id = PAGESETUP.thirdpartyids.shift(); var file = PAGESETUP.thirdpartydetails[id].src; if (file) { document.write(<div id="+id+-cache+" style="display:none;"> <div id="+id+-root+">); document.write(<script type="text/javascript" src="+file+" ></script>); } })(); } </script> <script type="text/javascript">document.write(</div></div>);</script>Sunday, January 16, 2011
  • 58. Rendering 3rd-party JavaScript: Part 2 - Render at the bottom of the page Get the registered ID <script type="text/javascript"> Get the registered if (PAGESETUP.thirdpartyids.length > 0) { (function() { JavaScript Call var id = PAGESETUP.thirdpartyids.shift(); var file = PAGESETUP.thirdpartydetails[id].src; if (file) { document.write(<div id="+id+-cache+" style="display:none;"> <div id="+id+-root+">); document.write(<script type="text/javascript" src="+file+" ></script>); } })(); Wrap the call in a } </script> hidden DIV <script type="text/javascript">document.write(</div></div>);</script>Sunday, January 16, 2011
  • 59. Rendering 3rd-party JavaScript: Part 2 - Render at the bottom of the page Get the registered ID <script type="text/javascript"> Get the registered if (PAGESETUP.thirdpartyids.length > 0) { (function() { JavaScript Call var id = PAGESETUP.thirdpartyids.shift(); var file = PAGESETUP.thirdpartydetails[id].src; if (file) { document.write(<div id="+id+-cache+" style="display:none;"> <div id="+id+-root+">); document.write(<script type="text/javascript" src="+file+" ></script>); } })(); Wrap the call in a } </script> hidden DIV <script type="text/javascript">document.write(</div></div>);</script> Close the DIVSunday, January 16, 2011
  • 60. In Summary  You Can Not Control Everything It’s OK, it really is :-)  Make What You Control FAST No DOM Event dependency Render components as soon as page is parsed Render components in priority  Treat Everything Else as a Black Box Process separately Delay inclusion as much as possible It’s about “mitigation” not “elimination”Sunday, January 16, 2011
  • 61. Inside Line ResultsSunday, January 16, 2011
  • 62. Inside Line Redesign Objectives PERFORMANCE (onLoad ≈1.5) RICHER CONTENT (Large Flash content serving photos and videos) BETTER REVENUE (Ad impression variance reduced by 3%)Sunday, January 16, 2011
  • 63. Another Vision Was BornSunday, January 16, 2011
  • 64. Legacy RedesignSunday, January 16, 2011
  • 65. Edmunds Legacy Edmunds Redesign 8.4 seconds 1.9 seconds ≈ 80% Reduction in Load Time!Sunday, January 16, 2011
  • 66. The Results 20% Total Page Views Per SessionSunday, January 16, 2011
  • 67. The Results 3% Ad Impression VarianceSunday, January 16, 2011
  • 68. The Results 4% Bounce RateSunday, January 16, 2011
  • 69. In The Works ...Sunday, January 16, 2011
  • 70. JavaScript Loader 2.0 EVEN FASTER! Load in parallel and execute on-demand COMPONENT METRICS (When it’s loaded, how long it took, ..etc) MANY ENHANCEMENTS :)Sunday, January 16, 2011
  • 71. Soon ... http://www.github.com/edmundsSunday, January 16, 2011
  • 72. What About The Future?Sunday, January 16, 2011
  • 73.  You Can Not Control Everything  Make What You Control FAST  Treat Everything Else as a Black Box How Will It Apply To ...Sunday, January 16, 2011
  • 74. Edmunds MobileSunday, January 16, 2011
  • 75. Edmunds PlatformSunday, January 16, 2011
  • 76. Edmunds Platform Plug-n-Play Widgets Syndication Managed Products raw data Content ® Standalone packaged data Tools Open Source Plugins Code Snippets Cars Mobile UI Handheld Wired UI API Backend SystemsSunday, January 16, 2011
  • 77. Edmunds Platform 3rd-party Consumer ® 3rd-party ProviderSunday, January 16, 2011
  • 78. Let’s Continue the Conversation @codeish http://tech.edmunds.comPhoto Credits:All Car Photos are from www.insideline.comhttp://www.cantronicsglobal.com/images/MissionVision.jpghttp://www.kewlwallpapers.com/images/wallpapers/John_C-680196.jpegSunday, January 16, 2011