<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Glenn Conner on Medium]]></title>
        <description><![CDATA[Stories by Glenn Conner on Medium]]></description>
        <link>https://medium.com/@mr_sharpoblunto?source=rss-176d0a02f80b------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*w7Dexcjl8a_rHczk.png</url>
            <title>Stories by Glenn Conner on Medium</title>
            <link>https://medium.com/@mr_sharpoblunto?source=rss-176d0a02f80b------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 07:33:39 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@mr_sharpoblunto/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Making instagram.com faster: Code size and execution optimizations (Part 4)]]></title>
            <link>https://medium.com/instagram-engineering/making-instagram-com-faster-code-size-and-execution-optimizations-part-4-57668be796a8?source=rss-176d0a02f80b------2</link>
            <guid isPermaLink="false">https://medium.com/p/57668be796a8</guid>
            <category><![CDATA[instagram]]></category>
            <category><![CDATA[react-native]]></category>
            <category><![CDATA[web-performance]]></category>
            <category><![CDATA[es2017]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Glenn Conner]]></dc:creator>
            <pubDate>Fri, 01 Nov 2019 13:03:12 GMT</pubDate>
            <atom:updated>2019-11-01T13:03:12.297Z</atom:updated>
            <content:encoded><![CDATA[<p>In recent years <a href="http://instagram.com/">instagram.com</a> has seen a lot of changes — we’ve launched stories, filters, creation tools, notifications, and direct messaging as well as a myriad of other features and enhancements. However, as the product grew, a side effect was that our web performance began to slow. Over the last year we made a conscious effort to improve this. This ongoing effort has thus far resulted in almost 50% cumulative improvement to our feed page load time. This series of blog posts will outline some of the work we’ve done that led to these improvements. In <a href="https://instagram-engineering.com/making-instagram-com-faster-part-1-62cc0c327538">part 1</a> we talked about prefetching data, in <a href="https://instagram-engineering.com/making-instagram-com-faster-part-2-f350c8fba0d4">part 2</a> we talked about improving performance by pushing data directly to the client rather than waiting for the client to request the data, and in <a href="https://instagram-engineering.com/making-instagram-com-faster-part-3-cache-first-6f3f130b9669">part 3</a> we talked about cache-first rendering.</p><h3>Code size and execution optimizations</h3><p>In parts 1–3 we covered various ways that we optimized the loading patterns of the critical path static resources and data queries. However there is another key area we haven’t covered yet that’s crucial to improving web application performance, particularly on low-end devices — <strong>ship less code to the user </strong>— in particular, <em>ship less JavaScript</em>. <br> <br> This might seem obvious, but there are a few points to consider here. There’s a common assumption in the industry that the size of the JavaScript that gets downloaded over the network is what’s important (i.e. the size <em>post-compression</em>), however we found that what’s really important is the size <em>pre-compression</em> as this is what has to be parsed and executed on the user’s device, even if it’s cached locally. This becomes particularly true if you have a site with many repeat users (and subsequent high browser cache hit rates) or users accessing your site on mobile devices. In these cases the parsing and execution performance of JavaScript on the CPU becomes the limiting factor rather than the network download time. For example, when we implemented <a href="https://en.wikipedia.org/wiki/Brotli">Brotli</a> compression for our JavaScript assets, we saw a nearly 20% reduction of post-compression size across the wire, but <strong><em>NO</em></strong> statistically significant reduction in the overall page load times as seen by end users. <br> <br> On the other hand, we’ve found reductions in pre-compression JavaScript size have consistently led to performance improvements. It’s also worth making a distinction between JavaScript that is executed on the critical path and JavaScript that is dynamically imported after the main page has completed. While ideally it would be nice to reduce the total amount of JavaScript in an application, a key thing to optimize in the short term is the amount of eagerly executed JavaScript on the critical path (we track this with a metric we call Critical Bytes Per Route). Dynamically imported JavaScript that lazy loads is generally not going to have as significant an effect on page load performance, so it’s a valid strategy to move non-visible or interaction dependent UI components out of the initial page bundles and into dynamically imported bundles. <br> <br> Refactoring our UI to reduce the amount of script on the critical path is going to be essential to improving performance in the long term — but this is a significant undertaking which will take time. In the short-term we worked on a number of projects to improve the size and execution efficiency of our existing code in ways that are largely transparent to product developers and require little refactoring of existing product code.</p><h3>Inline requires</h3><p>We bundle our frontend web assets using <a href="https://facebook.github.io/metro/">Metro</a> (the same bundler used by React Native) so we get access to inline-requires out of the box. Inline-requires moves the cost of requiring/importing modules to the first time when they are actually used. This means that you can avoid paying execution cost for unused features (though you’ll still pay the cost of downloading and parsing them) and you can better amortize the execution cost over the application startup, rather than having a large amount of upfront computation.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d5a33f7e41003f8608fb033886056f4f/href">https://medium.com/media/d5a33f7e41003f8608fb033886056f4f/href</a></iframe><p>To see how this works in practice, lets take the following example code:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/723e856b1fbaf18256da74cd88977ba5/href">https://medium.com/media/723e856b1fbaf18256da74cd88977ba5/href</a></iframe><p>Using inline requires this would get transformed into something like the following (you’ll find these inline requires by searching for r(d[ in the Instagram JS source in your browser developer tools)</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ef2f1d723b91b0778b00348042b3b307/href">https://medium.com/media/ef2f1d723b91b0778b00348042b3b307/href</a></iframe><p>As we can see, it essentially works by replacing the local references to a required module with a function call to require that module. This means that unless the code from that module is actually used, the module is never required (and therefore never executed). In most cases this works extremely well, but there are a couple of edge cases to be aware of that can cause problems — namely modules with side effects. For example:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7a43ec94aaf8dc6abb832293b35f608e/href">https://medium.com/media/7a43ec94aaf8dc6abb832293b35f608e/href</a></iframe><p>Without inline requires, Module C would output {&#39;foo&#39;:&#39;bar&#39;}, but when we enable inline-requires, it would output undefined, because B has an implicit dependency on A. This is a contrived example, but there are other real world cases where this can have effects i.e. what if a module does some logging as a part of its initialization - enabling inline-requires could cause this logging to stop happening. This is mostly preventable through linters that check for code that executes immediately at the module scope level, but there were some files we had to blacklist from this optimization such as runtime polyfills that need to execute immediately. After experimenting enabling inline requires across the codebase we saw an improvement in our Feed TTI (time to interactive) by 12% and Display Done by 9.8%, and decided that dealing with some of these minor edge cases was worth it for the performance improvements.</p><h3>Serving ES2017 bundles to modern browsers</h3><p>One of the primary drivers that drove the adoption of compiler/transpiler tools like Babel was allowing developers to use modern JavaScript coding idioms but still have their applications work in browsers that lacked native support for these latest language features. Since then a number of other important use-cases for these tools arose including compile-to-js languages like Typescript and ReasonML, language extensions such as JSX and Flow type annotations, and build time AST manipulations for things like internationalization. Because of this, it’s unlikely that this extra compilation step is going to go disappear from frontend development workflows any time soon. However, with that said it’s worth revisiting if the original purpose for doing this (cross browser compatibility) is still necessary in 2019.<br> <br> ES2015 and more recent features like async/await are now well supported across recent versions of most major browsers, so directly serving JavaScript containing these newer features is definitely possible — but there are two key questions that we had to answer first:</p><ul><li>Would enough users be able to take advantage of this to make the extra build complexity worthwhile (as you’d still need to maintain the legacy transpiling step for older browsers),</li><li>And what (if any) are the performance advantages of shipping ES2015+ features</li></ul><p>To answer the first question we first had to determine which features we were going to ship without transpiling/polyfilling and how many build variants we wanted to support for the different browsers. We settled on having two builds, one that would require support for ES2017 syntax, and a legacy build that would transpile back to ES5 (in addition we also added an optional polyfill bundle that would only be added for legacy browsers that lacked runtime support for more recent DOM API’s). Detecting support for these groups is done via some basic user-agent sniffing on the server side which ensures there is no runtime cost or extra roundtrip time from doing client-side detection of which bundles to load.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4971b12bb97da1ea896c832172f19d57/href">https://medium.com/media/4971b12bb97da1ea896c832172f19d57/href</a></iframe><p>With this in mind, we ran the numbers and determined that 56% of users to <a href="http://instagram.com/">instagram.com</a> are able to be served the ES2017 build without any transpiling or runtime polyfills, and considering that this percentage is only going to go up over time — it seems like its worth supporting two builds considering the number of users able to utilize it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/627/1*HDLCFPZCIfOpVWMSYz4HOg.png" /><figcaption>Percentage of Instagram users with ES2017 supported vs unsupported browsers</figcaption></figure><p>As for the second question — what are the performance advantages of shipping ES2017 directly — lets start by looking at what Babel actually does to transpile some common constructs back to ES5. In the left hand column is the ES2017 code, and on the right is the transpiled ES5 compatible version.</p><h4>Class (ES2017 vs ES5)</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/815/1*2_e9ARhf2RfO0rGy7aSA4w.png" /></figure><h4>Async/Await (ES2017 vs ES5)</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/818/1*WqgXcDXCe9r9xdhfcxPiQA.png" /></figure><h4>Arrow functions (ES2017 vs ES5)</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/815/1*_b476jWcR3BNPRErC7JlxQ.png" /></figure><h4>Rest parameters (ES2017 vs ES5)</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/817/1*WNRff6CfGqEAb4m1jZO_sQ.png" /></figure><h4>Destructuring assignment (ES2017 vs ES5)</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/818/1*Y8iA5py-J4NBdXPSfQ4cug.png" /></figure><p>From this we can see that there is a considerable size overhead in transpiling these constructs (even if you amortize the cost of some of the runtime helper functions over a large codebase). In the case of Instagram, we saw a 5.7% reduction in the size of our core consumer JavaScript bundles when we removed all ES2017 transpiling plugins from our build. In testing we found that the end-to-end load times for the feed page improved by 3% for users who were served the ES2017 bundle compared with those who were not.</p><h3>Still a long way to go</h3><p>While the progress that has been made so far is impressive, the work we’ve done so far represents just the beginning. Theres still a huge amount of room left for improvement in areas such as Redux store/reducer modularization, better code splitting, moving more JavaScript execution off the critical path, optimizing scroll performance, adjusting to different bandwidth conditions, and more. <br> <br> If you want to learn more about this work or are interested joining one of our engineering teams, please visit our <a href="https://www.facebook.com/careers/jobs/?q=instagram">careers page</a>, follow us <a href="https://www.facebook.com/instagramengineering/">on Facebook</a> or <a href="https://twitter.com/instagrameng">on Twitter</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=57668be796a8" width="1" height="1" alt=""><hr><p><a href="https://medium.com/instagram-engineering/making-instagram-com-faster-code-size-and-execution-optimizations-part-4-57668be796a8">Making instagram.com faster: Code size and execution optimizations (Part 4)</a> was originally published in <a href="https://medium.com/instagram-engineering">Instagram Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Making instagram.com faster: Part 3 — cache first]]></title>
            <link>https://medium.com/instagram-engineering/making-instagram-com-faster-part-3-cache-first-6f3f130b9669?source=rss-176d0a02f80b------2</link>
            <guid isPermaLink="false">https://medium.com/p/6f3f130b9669</guid>
            <category><![CDATA[instagram]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[redux]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[web-performance]]></category>
            <dc:creator><![CDATA[Glenn Conner]]></dc:creator>
            <pubDate>Fri, 11 Oct 2019 00:04:26 GMT</pubDate>
            <atom:updated>2019-10-11T00:05:55.736Z</atom:updated>
            <content:encoded><![CDATA[<h3>Making Instagram.com faster: Part 3 — cache first</h3><p>In recent years <a href="http://instagram.com/">instagram.com</a> has seen a lot of changes — we’ve launched stories, filters, creation tools, notifications, and direct messaging as well as a myriad of other features and enhancements. However, as the product grew, a side effect was that our web performance began to slow. Over the last year we made a conscious effort to improve this. This ongoing effort has thus far resulted in almost 50% cumulative improvement to our feed page load time. This series of blog posts will outline some of the work we’ve done that led to these improvements. In <a href="https://instagram-engineering.com/making-instagram-com-faster-part-1-62cc0c327538">part 1</a> we talked about prefetching data and in <a href="https://instagram-engineering.com/making-instagram-com-faster-part-2-f350c8fba0d4">part 2</a> we talked about improving performance by pushing data directly to the client rather than waiting for the client to request the data.</p><h4>Cache first</h4><p>Since we’re already pushing data to the client at the earliest possible time in the page load — the only faster way to get data to the client would be to not have to fetch or push any data at all. We can do this using a cache-first rendering approach, though this does mean that we have to display stale feed data to users for a short period of time. With this approach, when the page is loaded, we immediately present users with a cached copy of their previous feed and stories tray, and then replace it with fresh data once it’s available.</p><p>We use Redux to manage state on instagram.com, so at a high level the way we implemented this was to store a subset of our Redux store on the client in an indexedDB table, and then rehydrate the store when the page first loads. However, because of the asynchronous nature of indexedDB access, server data fetching, and user interactions, we can run into problems where the user interacts with the cached state, but then we want to ensure that those interactions are still applied to the new state when it arrives from the server.</p><p>For example, if we were to handle caching in a naive way we could run into the following problem: We begin loading from cache and from the network concurrently and since the cached feed is ready first, we display it to the user. The user then proceeds to like a post, but once the network response for the latest feed comes back it overwrites that post with a copy that doesn’t include the like action that the user applied to the cached copy (see the diagram below).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yw3otH31zNaPYawMbmbM7g.png" /><figcaption>Race conditions when the user interacts with cached data (Redux actions in green, state in grey)</figcaption></figure><p>To solve this issue, we needed a way to apply interactions to the cached state, but also store those interactions so they can be replayed later over the new state from the server. If you’ve ever used Git or similar source control systems before, this problem might seem familiar. If we think of the cached feed state as a branch, and the server feed response as master, what we effectively want to do is to do a rebase operation, applying the commits (likes, comments etc.) from our local branch onto the head of master.<br> <br> This brings us to the following design:</p><ul><li>On page load, we send a request for the new data (or wait for it to be pushed)</li><li>Create a staged subset of the Redux state</li><li>While the request/push is pending, we store any dispatched actions</li><li>Once the request resolves, we apply the action with the new data and any actions that have been pending to the staged state</li><li>When the staged state is committed, we simply replace the current state with the staged one.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NGABlwJjUR2g0T23xTvWoQ.png" /><figcaption>Fixing interaction race conditions with staging (Redux actions in green, state in grey)</figcaption></figure><p>By having a staging state, all the existing reducer behavior can be reused. It also keeps the staged state (which has the most recent data) separate from the current state. Also, since staging is implemented using Redux, we just need to dispatch actions to use it!</p><h4>API</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/58940fc0a5a6658f4825edd4fa46f069/href">https://medium.com/media/58940fc0a5a6658f4825edd4fa46f069/href</a></iframe><p>The staging API consists of two main functions: stagingAction &amp; stagingCommit (as well as a couple of others for handling reverts and edge cases that we won&#39;t cover here).</p><p>stagingAction accepts a promise that resolves an action to be dispatched to the staged state. It initializes the staging state and keeps track of any actions that have been dispatched since it was initialized. In the source control analogy we can think of this as creating a local branch as any actions that take place will now be queued and applied over the staged state when the new data arrives.</p><p>stagingCommit commits the staging state to the current state. If any async actions on the staging state are pending, it will wait before committing. This is similar to a rebase in source control terms, in that we apply all our local changes (from the cache branch) on top of master (the new data from the server), leaving our local branch up to date.</p><p>To enable staging, we wrap the root reducer with a reducer enhancer that handles the stagingCommit action and applies the staged actions to the new state. To use all this, we just need to dispatch the relevant actions and everything is handled for us. For example, if we want to fetch a new feed and apply it to a staged state, we can do something similar to the following:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/61252076a13255e5dd8f9d3f9292372a/href">https://medium.com/media/61252076a13255e5dd8f9d3f9292372a/href</a></iframe><p>Using cache-first rendering for both feed posts and the stories tray led to a 2.5% and 11% improvement in respective display done times and bought the user experience more in-line with what is available on the native iOS and android Instagram apps.</p><h4>Stay tuned for part 4</h4><p>In part 4 we’ll cover how we reduced the size of our codebase and improved its performance through code size and execution optimizations. If you want to learn more about this work or are interested joining one of our engineering teams, please visit our <a href="https://www.facebook.com/careers/jobs/?q=instagram">careers page</a>, follow us <a href="https://www.facebook.com/instagramengineering/">on Facebook</a> or <a href="https://twitter.com/instagrameng">on Twitter</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6f3f130b9669" width="1" height="1" alt=""><hr><p><a href="https://medium.com/instagram-engineering/making-instagram-com-faster-part-3-cache-first-6f3f130b9669">Making instagram.com faster: Part 3 — cache first</a> was originally published in <a href="https://medium.com/instagram-engineering">Instagram Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Making Instagram.com faster: Part 2]]></title>
            <link>https://medium.com/instagram-engineering/making-instagram-com-faster-part-2-f350c8fba0d4?source=rss-176d0a02f80b------2</link>
            <guid isPermaLink="false">https://medium.com/p/f350c8fba0d4</guid>
            <category><![CDATA[instagram]]></category>
            <category><![CDATA[web-performance]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Glenn Conner]]></dc:creator>
            <pubDate>Fri, 06 Sep 2019 16:47:03 GMT</pubDate>
            <atom:updated>2019-09-06T17:43:19.357Z</atom:updated>
            <content:encoded><![CDATA[<p>In recent years <a href="https://www.instagram.com/">instagram.com</a> has seen a lot of changes — we’ve launched stories, filters, creation tools, notifications, direct messaging and a myriad of other features and enhancements. However, as the product grew, one unfortunate side effect is that our web performance began to suffer. Over the last year we made a conscious effort to focus on improving this. This ongoing effort has thus far resulted in almost 50% cumulative improvement to our feed page load time. This series of blog posts will outline some of the work we’ve done that led to these improvements.</p><h4>Pushing data using early flushing and progressive HTML</h4><p>In <a href="https://instagram-engineering.com/making-instagram-com-faster-part-1-62cc0c327538">part 1</a>, we showed how using link preloads allows us to start dynamic queries earlier in the page load i.e. before the script that will initiate the request has even loaded. With that said, issuing these requests as a preload still means that the query will not begin until the HTML page has begun rendering on the client, which means the query cannot start until 2 network roundtrips have completed (plus however long it takes to generate the html response on the server). As we can see below for a preloaded GraphQL query, even though it’s one of the first things we preload in the HTML head, it can still be a significant amount of time before the query actually begins.</p><figure><img alt="A GraphQL request not being initiated for a long time after time to first byte despite preloading" src="https://cdn-images-1.medium.com/max/1024/1*eajwlBsSTE9KFVXQ7avohA.png" /></figure><p>The theoretical ideal is that we would want a preloaded query to begin execution as soon as the request for the page hits the server. But how can you get the browser to request something before it has even received any HTML back from the server? The answer is to push the resource from the server to the browser, and while it might look like HTTP/2 push is the solution here, there is actually a very old (and often overlooked) technique for doing this that has universal browser support and doesn’t have any of the infrastructural complexities of implementing HTTP/2 push. Facebook has been using this successfully since 2010 (see <a href="https://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919/">BigPipe</a>), as have other sites in various forms such as Ebay — but this technique seems to be largely ignored or unused by developers of JavaScript SPAs. It goes by a few names — early flush, head flushing, progressive HTML — and it works by combining two things:</p><ul><li>HTTP chunked transfer encoding</li><li>Progressive HTML rendering in the browser</li></ul><p><a href="https://en.wikipedia.org/wiki/Chunked_transfer_encoding">Chunked transfer encoding</a> was added as part of HTTP/1.1, and essentially it allows an HTTP network response to be broken up into multiple ‘chunks’ which can be streamed to the browser. The browser then stitches these chunks together as they arrive into a final completed response. While this does involve a fairly significant change to how pages are rendered on the server side, most languages and frameworks have support for rendering chunked responses (in the case of Instagram we use Django on our web frontends, so we use the <a href="https://docs.djangoproject.com/en/2.2/ref/request-response/#streaminghttpresponse-objects">StreamingHttpResponse</a> object). The reason this is useful is that it allows us to stream the contents of an HTML page to the browser as each part of the page completes — rather than having to wait for the whole response. This means we can flush the HTML head to the browser almost immediately (hence the term ‘early flush’) as it doesn’t involve much server-side processing. This allows the browser to start downloading scripts and stylesheets while the server is busy generating the dynamic data in the rest of the page. You can see the effect of this below.</p><figure><img alt="Waterfall chart showing dependent resources that don’t begin loading until the entire HTML document has been downloaded" src="https://cdn-images-1.medium.com/max/1016/1*mm8l384dt00zHnxiaxbDwg.png" /><figcaption>Without early flush: No resources load until the HTML is fully downloaded</figcaption></figure><figure><img alt="Waterfall chart showing early flushed dependent resources downloading before the entire HTML document has completed" src="https://cdn-images-1.medium.com/max/1017/1*fb5zkGO0ciGHimlIg7wm0A.png" /><figcaption>With early flush: Resources start loading as soon as the HTML tags are flushed to the browser</figcaption></figure><p>Additionally, we can use chunked encoding to send data to the client as it completes. In the case of server-side rendered applications this could be in the form of HTML, but we can push JSON data to the browser in the case of single page apps like instagram.com. To see how this works, let’s look at the naive case of a single page app starting up. First the initial HTML containing the JavaScript required to render the page is flushed to the browser. Once that script parses and executes, it will then execute an XHR query which fetches the initial data needed to bootstrap the page.</p><figure><img alt="Critical path for a typical JavaScript SPA — server producing HTML and the client running scripts and requesting API data" src="https://cdn-images-1.medium.com/max/835/1*NIDgYUXOjy4EJXpu3tEULw.png" /></figure><p>This process involves multiple roundtrips between the server and client and introduces periods where both the server and client are sitting idle. Rather than have the server wait for the client to request the API response, a more efficient approach would be for the server to start working on generating the API response immediately after the HTML has been generated and to push it to the client. This would mean that by the time the client has started up the data would likely be ready without having to wait for another round trip. The first step in making this change was to create a JSON cache to store the server responses. We implemented this by using a small inline script block in the page HTML that acts as a cache &amp; lists the queries that will be added to this cache by the server (this is shown in a simplified form below).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fc43fbc53fc3ce183f82544da3b7d72b/href">https://medium.com/media/fc43fbc53fc3ce183f82544da3b7d72b/href</a></iframe><p>After flushing the HTML to the browser the server can execute the API query itself and when it completes, flush the JSON data to the page as a script tag containing the data. When this HTML response chunk is received and parsed by the browser, it will result in the data being inserted into the JSON cache. A key thing to note with this approach is that the browser will render progressively as it receives response chunks (i.e. they will execute complete script blocks as they are streamed in). So you could potentially generate lots of data in parallel on the server and flush each response in its own script block as it becomes ready for immediate execution on the client. This is the basic idea behind Facebooks BigPipe system where multiple independent Pagelets are loaded in parallel on the server and pushed to the client in the order they complete.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1b307cb8e7ad8906d75ec21f2df3bdd9/href">https://medium.com/media/1b307cb8e7ad8906d75ec21f2df3bdd9/href</a></iframe><p>When the client script is ready to request its data, instead of issuing an XHR request, it first checks the JSON cache. If a response is present (or pending) it either responds immediately, or waits for the pending response.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/daff8f01d41245a5258efabffe1da7e1/href">https://medium.com/media/daff8f01d41245a5258efabffe1da7e1/href</a></iframe><p>This has the effect of changing the page load behavior to this:</p><figure><img alt="Critical path of a web app with early flushing of data, removing idle time where the client is waiting on the server for data" src="https://cdn-images-1.medium.com/max/852/1*SMeBpIo_8ylAiMDixmr5HA.png" /></figure><p>Compared to the naive loading approach, the server and client can now do more work in parallel — reducing idle periods where the server and client are waiting on each other. The impact of this was significant: desktop users experienced a 14% improvement in page display completion time, while mobile users (with higher network latencies) experienced a more pronounced 23% improvement.</p><h4>Stay tuned for part 3</h4><p>In part 3 we’ll cover how we further improved performance by taking a cache first approach to rendering data. If you want to learn more about this work or are interested joining one of our engineering teams, please visit our <a href="https://www.facebook.com/careers/jobs/?q=instagram">careers page</a>, follow us <a href="https://www.facebook.com/instagramengineering/">on Facebook</a> or <a href="https://twitter.com/instagrameng">on Twitter</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f350c8fba0d4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/instagram-engineering/making-instagram-com-faster-part-2-f350c8fba0d4">Making Instagram.com faster: Part 2</a> was originally published in <a href="https://medium.com/instagram-engineering">Instagram Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Making Instagram.com faster: Part 1]]></title>
            <link>https://medium.com/instagram-engineering/making-instagram-com-faster-part-1-62cc0c327538?source=rss-176d0a02f80b------2</link>
            <guid isPermaLink="false">https://medium.com/p/62cc0c327538</guid>
            <category><![CDATA[instagram]]></category>
            <category><![CDATA[web-performance]]></category>
            <category><![CDATA[web-development]]></category>
            <dc:creator><![CDATA[Glenn Conner]]></dc:creator>
            <pubDate>Thu, 01 Aug 2019 16:50:16 GMT</pubDate>
            <atom:updated>2019-08-01T16:50:16.536Z</atom:updated>
            <content:encoded><![CDATA[<p>In recent years, <a href="http://instagram.com/">instagram.com</a> has seen a lot of changes — we’ve launched stories, filters, creation tools, notifications, and direct messaging as well as myriad other features and enhancements. However, as the product grew, one unfortunate side effect was that our web performance began to suffer. Over the last year, we made a conscious effort to improve this. Our ongoing efforts have thus far resulted in almost 50% cumulative improvement to our feed page load time. This series of blog posts will outline some of the work we’ve done that led to these improvements.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*madjA8Cbk-QVS--NyR5Q3g.png" /><figcaption>Performance improvements over the last year for feed page Display Done times (milliseconds)</figcaption></figure><p>Correctly prioritizing resource download and execution and reducing browser downtime during the page load is one of the main levers for improving web application performance. In our case, many of these types of optimizations proved to be more immediately impactful than code size reductions, which tended to be individually small and only began to add up after many incremental improvements (though we will talk about these in future installments). They were also less disruptive to product development, requiring less code change and refactoring. So initially, we focused our efforts in this area, beginning with resource prefetching.</p><h4>JavaScript, XHR, and image prefetching (and how you need to be careful)</h4><p>As a general principle, we want to inform the browser as early as possible about what resources are required to load the page. As developers, we often know what resources we are going to need ahead of time, but the browser may not become aware of those until late in the page loading process. These resources mainly include those that are dynamically fetched by JavaScript (other scripts, images, XHR requests etc.) since the browser is unable to discover these dependent resources until it has parsed and executed some other JavaScript first. <br> <br>Instead of waiting for the browser to discover these resources itself, we can provide a hint to the browser that it should start working on fetching those resources immediately. The way we do this is by using HTML preload tags. They look something like this:</p><pre>&lt;link rel=&quot;preload&quot; href=&quot;my-js-file.js&quot; as=&quot;script&quot; type=&quot;text/javascript&quot; /&gt;</pre><p>At Instagram, we use these preload hints for two types of resources on the critical page loading path: dynamically loaded JavaScript and preloading XHR GraphQL requests for data. Dynamically loaded scripts are those that are loaded via import(&#39;...&#39;) for a particular client-side route. We maintain a list of mappings between server-side entrypoints and client-side route scripts — so when we receive a page request on the server-side, we know which client-side route scripts will be required for a particular server-side entrypoint and we can add a preload for these route specific scripts as we render the initial page HTML.<br> <br>For example, for the FeedPage entrypoint, we know that our client-side router will eventually make a request for FeedPageContainer.js, so we can add the following:</p><pre>&lt;link rel=&quot;preload&quot; href=&quot;/static/FeedPageContainer.js&quot; as=&quot;script&quot; type=&quot;text/javascript&quot; /&gt;</pre><p>Similarly, if we know that a particular GraphQL request is going to be made for a particular page entrypoint, then we should preload that XHR request. This is particularly important as these GraphQL queries can sometimes take a long time and the page can&#39;t render until this data is available. Because of this, we want to get the server working on generating the response as early as possible in the page lifecycle.</p><pre>&lt;link rel=&quot;preload&quot; href=&quot;/graphql/query?id=12345&quot; as=&quot;fetch&quot; type=&quot;application/json&quot; /&gt;</pre><p>The changes to the page load behavior are more obvious on slower connections. With a simulated fast 3G connection (the first waterfall below -without any preloading), we see that FeedPageContainer.js and its associated GraphQL query only begin once Consumer.js has finished loading. However, in the case of preloading, both FeedPageContainer.js and its GraphQL query can begin loading as soon as the page HTML is available. This also reduces the time to load any non-critical lazy loaded scripts, which can be seen in the second waterfall. Here FeedSidebarContainer.js and ActivityFeedBox.js (which depend upon FeedPageContainer.js) begin loading almost immediately after Consumer.js has completed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*a8XZMWiI8l4ZmP466LHe6A.png" /><figcaption>Without preloading</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fLgPDpsFSDrjTNB86Jr__g.png" /><figcaption>With preloading</figcaption></figure><h4>Benefits of preload prioritization</h4><p>In addition to starting the download of resources sooner, link preloads also have the additional benefit of increasing the network priority of async script downloads. This becomes important for async scripts on the critical loading path because the default priority for these is <em>Low. </em>This<em> </em>means that XHR requests and images in the viewport will have higher network priority, and images outside the viewport will have the same network priority. This can cause situations where critical scripts required for rendering the page are blocked or have to share bandwidth with other resources (if you’re interested, see <a href="https://medium.com/reloading/preload-prefetch-and-priorities-in-chrome-776165961bbf">here</a> for an in-depth discussion of resource priorities in Chrome). Careful use (more on that in a minute) of preloads gives an important level of control over how we want the browser to prioritize content during initial loads in cases where we know which resources should be prioritized.</p><h4>Problems with preload prioritization</h4><p>The problem with preloads is that with the extra control it provides, comes the extra responsibility of correctly setting the resource priorities. For example, when testing in regions with very slow overall mobile and wifi networks and significant packet loss, we noticed that &lt;link rel=&quot;preload&quot; as=&quot;script&quot;&gt; network requests for scripts were being prioritized over the &lt;script /&gt; tags of the JavaScript bundles on the critical page rendering path, resulting in an increase in overall page load time.<br> <br>This stemmed from how we were laying out the preload tags on our pages. We were only putting preload hints for bundles that were going to be downloaded asynchronously as part of the current page by the client-side router.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8a4e2db24f0f3987876c5893ffc0463a/href">https://medium.com/media/8a4e2db24f0f3987876c5893ffc0463a/href</a></iframe><p>In the example for the logged out page, we were prematurely downloading (preloading) SomeConsumerRoute.js before Common.js &amp; Consumer.js and since preloaded resources are downloaded with the highest priority but are not parsed/compiled, they blocked Common &amp; Consumer from being able to start parsing/compiling. The Chrome Data saver team also found similar issues with preloads and wrote about their solution <a href="https://medium.com/reloading/a-link-rel-preload-analysis-from-the-chrome-data-saver-team-5edf54b08715">here</a>. In their case, they opted to always put preloads for async resources <em>after</em><strong> </strong>the script tag of the resource that requests them. In our case we opted for a slightly different approach. We decided to have a preload tag for <strong>all</strong> script resources and to place them in the order that they would be needed. This ensured that we were able to start preloading all script resources as early as possible in the page (including synchronous script tags that couldn’t be rendered into the HTML until after certain server side data was added to the page), and ensured that we could control the ordering of script resource loading.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/baa3676cebdeda0ba8304d81406225c0/href">https://medium.com/media/baa3676cebdeda0ba8304d81406225c0/href</a></iframe><h4>Image prefetching</h4><p>One of the main surfaces on <a href="http://instagram.com/">instagram.com</a> is the Feed, consisting of an infinite scrolling feed of images and videos. We implement this by loading an initial batch of posts and then loading additional batches as the user scrolls down the feed. However, we don’t want the user to wait every time they get to the bottom of the feed (while we load a new batch of posts), so it’s very important for the user experience that we load in new batches before the user hits the end of their current feed.<br> <br> This is quite tricky to do in practice for a few reasons:</p><ul><li>We don’t want off-screen batch loading to take CPU and bandwidth priority away from parts of the feed the user is currently viewing.</li><li>We don’t want to waste user bandwidth by being over-eager with preloading posts the user might not ever bother scrolling down to see, but on the other hand if we don’t preload enough, the user will frequently hit the end of feed.</li><li><a href="http://instagram.com/">Instagram.com</a> is designed to work on a variety of screen sizes and devices, so we display feed images using the img srcset attribute (which lets the browser decide which image resolution to use based on the users screen size). This means it&#39;s not easy to determine which image resolution we should preload &amp; risks preloading images the browser will never use.</li></ul><p>The approach we used to solve the problem was to build a prioritized task abstraction that handles queueing of asynchronous work (in this case, a prefetch for the next batch of feed posts). This prefetch task is initially queued at an idle priority (using requestIdleCallback), so it won’t begin unless the browser is not doing any other important work. However if the user scrolls close enough to the end of the current feed, we increase the priority of this prefetch task to ‘high’ by cancelling the pending idle callback and thus firing off the prefetch immediately.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1005/1*im7W6V2uWcsIa7L5JHjgXA.png" /></figure><p>Once the JSON data for the next batch of posts arrives, we queue a sequential background prefetch of all the images in that preloaded batch of posts. We prefetch these sequentially in the order the posts are displayed in the feed rather than in parallel, so that we can prioritize the download and display of images in posts closest to the user’s viewport. In order to ensure we actually prefetch the correct size of the image, we use a hidden media prefetch component whose dimensions match the current feed. Inside this component is an &lt;img&gt; that uses a srcset attribute, the same as for a real feed post. This means that we can leave the selection of the image to prefetch up to the browser, ensuring it will use the same logic for selecting the correct image to display on the media prefetch component as it does when rendering the real feed post. It also means that we can prefetch images on other surfaces on the site — such as profile pages — as long as we use a media prefetch component set to the same dimensions as the target display component.<br> <br>The combined effect of this was a 25% reduction in time taken to load photos (i.e. the time between a feed post being added to the DOM and the image in that post actually loading and becoming visible) as well as a 56% reduction in the amount of time users spent waiting at the end of their feed for the next page.</p><h4>Stay tuned for part 2</h4><p>Stay tuned for part 2: Early flushing and progressive HTML (and why you don’t necessarily need HTTP/2 push for pushing resources to the browser). If you want to learn more about this work or are interested in joining one of our engineering teams, please visit our <a href="https://www.facebook.com/careers/jobs/?q=instagram">careers page</a>, follow us <a href="https://www.facebook.com/instagramengineering/">on Facebook</a> or <a href="https://twitter.com/instagrameng">on Twitter</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=62cc0c327538" width="1" height="1" alt=""><hr><p><a href="https://medium.com/instagram-engineering/making-instagram-com-faster-part-1-62cc0c327538">Making Instagram.com faster: Part 1</a> was originally published in <a href="https://medium.com/instagram-engineering">Instagram Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>