<?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[Web Performance for Developers - Medium]]></title>
        <description><![CDATA[Developer tips across the full stack for building web applications for high performance - Medium]]></description>
        <link>https://medium.com/web-performance-for-developers?source=rss----43285a67f794---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Web Performance for Developers - Medium</title>
            <link>https://medium.com/web-performance-for-developers?source=rss----43285a67f794---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 20 Oct 2026 15:28:34 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/web-performance-for-developers" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Caching: Understating the scope]]></title>
            <link>https://medium.com/web-performance-for-developers/caching-understating-the-scope-c97e7197911e?source=rss----43285a67f794---4</link>
            <guid isPermaLink="false">https://medium.com/p/c97e7197911e</guid>
            <category><![CDATA[caching]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[application-development]]></category>
            <category><![CDATA[cache]]></category>
            <dc:creator><![CDATA[Leon Fayer]]></dc:creator>
            <pubDate>Fri, 16 Nov 2018 15:01:33 GMT</pubDate>
            <atom:updated>2018-11-16T15:01:32.637Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JfVmFl0nxqCtSfIlUDfb4A.jpeg" /></figure><blockquote>It’s not what you look at that matters, it’s what you see. — H.D. Thoreau</blockquote><p>Most performance conversations, at one point or another, will get to the subject of caching. And for a good reason. Caching helps to hide some of the biggest performance inefficiency in many systems. That’s right, <em>hide </em>not <em>fix</em>. Whether it’s <a href="https://medium.com/web-performance-for-developers/local-personalizations-7c0089cb5d15">local storage</a> of data, <a href="https://medium.com/web-performance-for-developers/caching-file-system-737155533c5c">pre-calculated data set</a> or a <a href="https://medium.com/web-performance-for-developers/caching-cache-control-header-3d0c6de0b711">transient storage</a> to reduce a number of server-side requests, all these methods are improving performance by limiting the frequency of expensive operations. Again, it is important to remember,<em> cache does not improve the performance of the operations themselves, but rather reduces/delays/eliminates the need to execute those operations.</em></p><p>Don’t get me wrong, limiting a number of expensive connections or operations is important, this is why caching is a common component in most modern architectures. This is why it’s somewhat surprising to me that other good practice techniques that accomplish similar goals, but don’t fall under traditional definition of “caching”, often get ignored. Specifically, I am talking about scope caching™.</p><h4>What is “scope caching”™?</h4><p>Scope caching™ is something that most of you familiar with programming are likely already doing in some capacity. The simplest example of scope caching is a basic programming principle of <a href="https://medium.com/web-performance-for-developers/reducing-property-access-aa546a325612">not putting computational operations in a loop</a> where possible. Running operations outside the loop, storing it in the variable and using a result variable in the following loop/code path is an example of reuse of pre-computed value. Which, in essence, what caching is trying to accomplish.</p><h4>How many scopes are there?</h4><p>Many. As many scopes as there are in a programming language and than some. That’s why it’s important to take time and understand the needed <em>scope</em> of cache requirements.</p><p>For example, scoping a variable globally is considered a bad practice. However, when working with a web language, global scope could be a webserver thread or webserver session. Loading something once and keeping it for the life of that scope for all the users can (and should) be completely acceptable solution. Pre-generated constants is a good example. If you (like many) store your countries and states in the database, loading them during a web server start and keeping them in global memory, instead of going to the database on every request, is a form of scope caching.</p><p>And speaking of web, REQUEST and SESSION are probably the two most common scopes that can be taken advantage of.</p><p>Consider a function that sits on top of auth service, that returns a current user object. It’s really simple. Useful. Widely used.</p><pre>public function currentUser() {<br><strong>  var currentUser = classes.getCurrentUser();<br></strong>  return currentUser;<br>}</pre><p>A lot of different modules and services take advantage of it to validate user’s access control, setting, etc. And because of that, it’s being called, at minimum, <em>14 times per page load. </em>14 requests to your database/cache/user service, over the network, to retrieve the exact same information on a single page. Service design ftw.</p><p>Problem? Of course. But an easily solvable problem, without the need to re-engineer the service.</p><pre>public function currentUser() {<br>  if ( <strong>!isDefined(&quot;REQUEST.currentUser&quot;)</strong> ) {<br>    <strong>REQUEST.currentUser = classes.getCurrentUser();<br>  </strong>}<br>  return REQUEST.currentUser;<br>}</pre><p>Storing a result of the first-retrieved user object in a REQUEST variable, persistent for the duration of that, well, request, will make the information available to different consumers on the same page without a round trip to the source.</p><p>Scope caching™!</p><p>The list of scopes above is not exhaustive, although it (hopefully) provides a good illustration of the diversity of scope levels and techniques to limit operations. Remember, if you need to ask for the information more than once, make sure to cache the response. (hey, it rhymed!)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c97e7197911e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/web-performance-for-developers/caching-understating-the-scope-c97e7197911e">Caching: Understating the scope</a> was originally published in <a href="https://medium.com/web-performance-for-developers">Web Performance for Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Local Personalizations]]></title>
            <link>https://medium.com/web-performance-for-developers/local-personalizations-7c0089cb5d15?source=rss----43285a67f794---4</link>
            <guid isPermaLink="false">https://medium.com/p/7c0089cb5d15</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[ecommerce-web-development]]></category>
            <category><![CDATA[web-performance]]></category>
            <category><![CDATA[personalization]]></category>
            <category><![CDATA[performance]]></category>
            <dc:creator><![CDATA[Leon Fayer]]></dc:creator>
            <pubDate>Mon, 06 Aug 2018 18:09:26 GMT</pubDate>
            <atom:updated>2018-08-06T18:09:25.783Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FlHXS4oDt8H5BAD97CNBCw.png" /></figure><p>Unless you are running a complete brochureware site, chance are, you have some sort of content personalization requirement, whether it’s user’s name, number of items in the shopping cart, custom navigation or something entirely different, specific to a particular user.</p><p>Let’s examine a prototype of a standard e-commerce personalization logic.</p><p>When a user logs in, on success, we store their identifier in a session variable (commonly — browser cookie). When that user navigates to a different page, we retrieve user information (to acquire first name to display) based on the identifier stored in a local cookie and then, if the user exists, we pull user’s shopping cart information to display a number and, in this case, amount of items in the cart.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/470/1*9kGNwcYMDalVS0HwwsBeSg.png" /></figure><p>From a code perspective, the logic is pretty simple. You add a few line snippet of code to your header and on every page you now display information personal to that particular user. Easy.</p><pre>var loggedUser <strong>:=</strong> user<strong>.</strong>New(cookie)</pre><pre>if len(loggedUser.firstName) &gt; 0 {</pre><pre>  loggedUser.fillShoppingCart()</pre><pre>}</pre><p>But the ease of implementation comes at a cost of a performance hit. First of, we just added two new lookups to <em>every</em> page. Furthermore, because <em>every</em> page is now a page that has dynamic content, pages <a href="https://medium.com/web-performance-for-developers/caching-cache-control-header-3d0c6de0b711">cannot be <em>easily</em> cached</a>. Which, in turn, means that every page you serve will <em>always</em> need to perform multiple lookups to retrieve the information needed to be custom for that individual user. Which sucks.</p><p>Note, the lookup implementation logic for both user and shopping cart information can vary. Most commonly, those calls query the database for the data. Information can also be retrieved from APIs (both internal and third-party), which, given JavaScript AJAX lookup ability, can be done completely in the front end code, partially solving “no caching” issue. However, API lookups come with a set of <a href="https://medium.com/web-performance-for-developers/external-dependencies-26c3b8ac91e9">additional performance challenges</a> that need to be considered.</p><p>Luckily, in a lot of cases, those lookups can be minimized, if not eliminated altogether.</p><p><strong>Store all the information you need in cookies.</strong></p><p>That’s it. For the example above, in addition to storing user id in cookies, also store user’s first name. And update cart counter (and maybe an amount) cookie every time the user updates the cart. This way, instead of doing server lookups, the information will be available directly in user’s browser.</p><p>There, of course, may be a reasonable concern about data tempering. Since browser cookies can be altered by a user, a concern would be that shopping cart data can be manipulated to rig the checkout process. An important thing to remember, the actual shopping cart page doesn’t have to (nor is recommended to) rely on the cookie for item information. This solution is intended to reduce the number of request calls that would’ve been made by <em>every</em> page. A database or an API lookup on a single page is not a problem we’re solving. Or should really be trying to solve in this case. Checkout process is one of the areas where a small performance hit is worth the data immutability guarantee.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7c0089cb5d15" width="1" height="1" alt=""><hr><p><a href="https://medium.com/web-performance-for-developers/local-personalizations-7c0089cb5d15">Local Personalizations</a> was originally published in <a href="https://medium.com/web-performance-for-developers">Web Performance for Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[External dependencies]]></title>
            <link>https://medium.com/web-performance-for-developers/external-dependencies-26c3b8ac91e9?source=rss----43285a67f794---4</link>
            <guid isPermaLink="false">https://medium.com/p/26c3b8ac91e9</guid>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[dependencies]]></category>
            <category><![CDATA[services]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[web-development]]></category>
            <dc:creator><![CDATA[Leon Fayer]]></dc:creator>
            <pubDate>Tue, 03 Apr 2018 18:27:00 GMT</pubDate>
            <atom:updated>2018-04-12T14:36:21.382Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lN5YvJTQtq5LpYUkMVmI2g.jpeg" /><figcaption>Columbia, Maryland © Leon Fayer</figcaption></figure><p>In this day and age, more and more applications leverage external services. So much so, that only about 6% of all sites has no 3rd party content and <a href="https://discuss.httparchive.org/t/what-is-the-distribution-of-1st-party-vs-3rd-party-resources/100/11">almost 40% of all site have between 75% and 99% 3rd party content</a>. And for a good reason. External services provide a tremendous benefit, not only in brand familiarity and content, but also in cost saving, considering you’re leveraging years of someone else’s work for your gain.</p><p>However, one of the major risks when implementing a third party service is the reliance on the availability of that third party service. Availability of your production site. Based on the service that you have no control over. And no matter how large or successful the service that you’re using is — it will <a href="https://medium.com/web-performance-for-developers/caching-cache-control-header-3d0c6de0b711">go down</a> (or become unavailable) at one point or another. Perhaps the best example of it is using Google CDN for jQuery, only to realize that your site will break for all Chinese users, since, you know, China blocks all traffic coming from Google.</p><p>And so we have a conundrum. We should (and do) use 3rd party services, sometimes extensively, yet our systems become more volatile because of that. Luckily there are a few golden rules to follow when implementing 3rd party service.</p><ol><li><strong>Only connect to a third-party service where needed.</strong> Don’t try to connect to SSO on every page load to validate that a user is still, in fact, the same user you displayed the previous page to. Cache the results locally.</li><li><strong>Avoid external connections on critical path. </strong>This is the core idea behind putting all your JavaScript in a footer of a webpage. Don’t load Google Analytics as a first thing on your page, you will delay the display of the content that actually matters. Make the connections after your content is loaded, or better yet — connect asynchronously.</li><li><strong>Trap time-outs <em>and </em>errors.</strong> You do it with your database connections, why would you treat external connections differently?</li><li><strong>Create a fallback plan.</strong> You have no control over external services, but you do have the control over the content presented to your users. If external images are the essential feature of your site — store them locally after the pull, so you can fall back to the latest available content in case the service is unavailable. Remember, often times stale content is better then no content at all.</li></ol><p>So how does it all translate to a real example? Let’s take a basic example of pulling data from external source in JavaScript with jQuery XHR call.</p><pre>$(function () {<br>  $(document).bind(&quot;ajaxComplete&quot;, function (e, xhr) {<br>      if (xhr.statusText == &#39;timeout&#39;) {<br>        <em>// pull local content<br>      </em>} else {<br>        <em>// use content from response<br>      </em>}<br>  });<br>        <br>  $.ajax({<br>    url: &#39;http://example.com/feed.json&#39;,<br>    dataType: &#39;json&#39;,<br>    success: function (data, status, xhr) {<br>      <em>// generate consumable content from response</em><br>    },<br>    error: function (xhr, status, err) {<br>      <em>// pull local content</em><br>    },<br>    timeout: 3<br>  });<br>});</pre><p>XHR requests are asynchronous by nature, which automatically removes the load time from critical path, allowing <em>your </em>content to be loaded independent of 3rd party service latency. Error trapping also accounts for blatant service unavailability or problem by pulling local content if error is detected. And a part that most people forget, latency detection is also enforced via timeout flag and check of response status.</p><p>That’s all. A couple of parting thoughts:</p><ul><li>In modern web applications external dependencies can be nested. Use the sanity check list above for all of them.</li><li>Latency is a <a href="http://fayerplay.com/breaking-social-dependency/">performance killer</a>. Make sure you utilize <a href="https://medium.com/web-performance-for-developers/caching-cache-control-header-3d0c6de0b711">caching</a> to reduce number of external requests to an absolute minimum.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=26c3b8ac91e9" width="1" height="1" alt=""><hr><p><a href="https://medium.com/web-performance-for-developers/external-dependencies-26c3b8ac91e9">External dependencies</a> was originally published in <a href="https://medium.com/web-performance-for-developers">Web Performance for Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Content separation]]></title>
            <link>https://medium.com/web-performance-for-developers/content-separation-bd00aeaaea35?source=rss----43285a67f794---4</link>
            <guid isPermaLink="false">https://medium.com/p/bd00aeaaea35</guid>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[apache]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[configuration]]></category>
            <category><![CDATA[nginx]]></category>
            <dc:creator><![CDATA[Leon Fayer]]></dc:creator>
            <pubDate>Wed, 03 Jan 2018 18:13:44 GMT</pubDate>
            <atom:updated>2018-06-18T02:35:09.546Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5YOxY1AtXJQUGAKl4OwuXA.jpeg" /><figcaption>Penn Station, New York © Leon Fayer</figcaption></figure><p>Every type of content was not created equal. Nor should it be served the same way. At a high level, you can separate your content into 3 groups:</p><ul><li><strong>infinitely static </strong>— image is a perfect example of this. Once an image has been published, you should never update it (because multiple reasons) and should be able to cache it almost indefinitely.</li><li><strong>temporarily static</strong> — this covers the content that renders into static output. CSS, articles, etc. The content that may change in the future (requiring you to purge cache) but generally can be cached on demand for extended period of time.</li><li><strong>dynamic</strong> — the content that is individual for each user/load. Personalization, user profile/history, top/recent content — all these cannot be cached for any extended period of time and require re-computation or rendering on each request.</li></ul><p>Not only those groups should be <a href="https://medium.com/web-performance-for-developers/caching-cache-control-header-3d0c6de0b711">treated differently</a> from cache strategy perspective, but they should also be treated differently when serving them from origin.</p><h3>The overhead</h3><p>This applies to any web server (nginx, node.js, Apache), but for the ease of example, let’s use Apache. Chances are, your httpd.conf starts with something like this (with correction to language used).</p><pre><em># load modules</em><br>LoadModule perl_module /opt/apache22/libexec/amd64/mod_php.so<br>LoadModule apreq_module /opt/apache22/libexec/amd64/mod_apreq2.so<br>LoadModule apreq_module /opt/apache22/libexec/amd64/mod_ssl.so</pre><pre><em># serve on port 80 for non-ssl content</em><br>Listen 0.0.0.0:80</pre><pre><em># setup mod_rewrite engine</em><br>RewriteEngine On<br>RewriteLogLevel 4<br>RewriteLog /www/logs/apache/rewrites.log</pre><pre><em># validate against rewrite checks</em><br>Include /www/etc/httpd-rewrite-global.conf</pre><pre><em># define server/thread limits</em><br>StartServers 5<br>ServerLimit 40<br>MaxClients 40<br>MinSpareServers 5<br>MaxSpareServers 10</pre><pre><em># if the file doesn&#39;t exist is static directory <br># process dynamic content through the dispatcher function<br></em>RewriteCond /www/htdocs/static/$1 !-f<br>RewriteRule /(.*)$ /www/htdocs/dynamic/dispatcher.php [L]</pre><pre><em># if file is static and is found - serve it</em><br>RewriteRule /(.*)$ /static/$1 [L]</pre><p>Most people don’t think about it, but with this setup, <em>every request</em> is going to load (or use) required modules and try to compare the requested URL to a (usually) long list of rewrite rules (that have a tendency to accumulate rather quickly). So the question you have to ask yourself is — does serving logo.png require loading mod_php module and does it need to be matched against dozens of legacy URL strings the business decided to keep active after the last redesign? Or, in fewer words, does every static asset need the same overhead as dynamic content? The answer is — no, no it does not.</p><p>Furthermore, is the service/thread configuration you use for your dynamic content is the same as you would need to serve your assets from origin? And again, the answer is likely no.</p><h3>Separation of responsibility</h3><p>Separating the serving of different types of content allows you to optimize and tune serve time for each <em>individual</em> type of content. The way to accomplish it is to have individual web servers running for each type of content that requires individual optimization. For example, instead of having a generic httpd.conf that listens to requests on port 80, you need to create httpd-static.conf listening on port 80 and httpd-dynamic.conf listening on different port (let’s say 8081).</p><p><strong>httpd-static.conf</strong></p><pre>Listen 0.0.0.0:80</pre><pre><em># static content configuration parameters</em><br>StartServers 2<br>ServerLimit 7<br>ThreadLimit 1200<br>ThreadsPerChild 200<br>MaxClients 1200<br>MaxRequestsPerChild 0<br>MaxSpareThreads 1200</pre><pre>RewriteEngine On</pre><pre><em># If it’s a local static file, go ahead and serve it, <br># otherwise send to dynamic web server</em><br>RewriteCond /www/htdocs/static/%{REQUEST_URI} !-f<br>RewriteRule ^(.*)$ http://localhost:8081/$1 [P,L]</pre><p><strong>httpd-dynamic.conf</strong></p><pre><em># load modules</em><br>LoadModule perl_module /opt/apache22/libexec/amd64/mod_php.so<br>LoadModule apreq_module /opt/apache22/libexec/amd64/mod_apreq2.so<br>LoadModule apreq_module /opt/apache22/libexec/amd64/mod_ssl.so</pre><pre><em># serve on port 8081 for non-ssl dynamic content</em><br>Listen 0.0.0.0:8081</pre><pre><em># setup mod_rewrite engine</em><br>RewriteEngine On<br>RewriteLogLevel 4<br>RewriteLog /www/logs/apache/rewrites.log</pre><pre><em># validate against rewrite checks</em><br>Include /www/etc/httpd-rewrite-global.conf</pre><pre><em># define server/thread limits</em><br>StartServers 5<br>ServerLimit 40<br>MaxClients 40<br>MinSpareServers 5<br>MaxSpareServers 10</pre><pre><em># serve content through dispatcher</em><br>RewriteRule /(.*)$ /www/htdocs/dynamic/dispatcher.php [L]</pre><p>Use httpd-static.conf as a gateway, to quickly serve static content and pass on the dynamic content to httpd-dynamic.conf to do the processing.</p><p>You can configure static content to also honor individual extensions instead of files from certain directory. And in addition to having separate configuration parameters, tuned to individual types of content, this allows to easily add different logging/monitoring/caching rules to different types of content.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bd00aeaaea35" width="1" height="1" alt=""><hr><p><a href="https://medium.com/web-performance-for-developers/content-separation-bd00aeaaea35">Content separation</a> was originally published in <a href="https://medium.com/web-performance-for-developers">Web Performance for Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Reducing property access]]></title>
            <link>https://medium.com/web-performance-for-developers/reducing-property-access-aa546a325612?source=rss----43285a67f794---4</link>
            <guid isPermaLink="false">https://medium.com/p/aa546a325612</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[tips-and-tricks]]></category>
            <dc:creator><![CDATA[Leon Fayer]]></dc:creator>
            <pubDate>Mon, 04 Dec 2017 17:08:52 GMT</pubDate>
            <atom:updated>2017-12-04T17:08:51.757Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*r_wXyIoxOntbviGeALZPVA.jpeg" /><figcaption>Prague, Czech Republic © Leon Fayer</figcaption></figure><p>Loops are generally <a href="https://medium.com/web-performance-for-developers/n-1-problem-c8911bfd2577">not great</a> from the performance perspective, but often times it’s relatively easy to pinpoint and optimize those inefficient function you wrote and call inside of a loop. But sometimes, especially with JavaScript, the functions causing the performance hit are the ones that are provided to you by language.</p><p>Let’s look at the most basic example of taking an array of items and adding them to your html table.</p><pre>for (var i = 0; i &lt; items.length; i++) {<br>  document.getElementById(&quot;listGrid&quot;).innerHTML += &quot;&lt;tr&gt;&lt;td&gt;&quot; + <br>                                                   items[i].name + <br>                                                   &quot;&lt;/td&gt;&lt;/tr&gt;&quot;;<br>}</pre><p>This is pretty straight forward. Iterate through the array and inject each item name into a listGrid table . There are no special functions to worry about, this loop is based purely on JavaScript-provided functions for doing exactly those things. And yet, there is still room for optimization.</p><h4>Reduce property lookup overhead</h4><p>First of all, let’s look at the loop definition itself. The instruction is to iterate from index 0 to last element of the loop, as defined by the array function length. Problem here is that items.length property is being accessed on every iteration of the loop, for really no reason. So let’s take it outside the loop.</p><pre><strong>var itemArrLen = items.length;</strong></pre><pre>for (var i = 0; i &lt; <strong>itemArrLen</strong>; i++) {<br>  document.getElementById(&quot;listGrid&quot;).innerHTML += &quot;&lt;tr&gt;&lt;td&gt;&quot; + <br>                                                   items[i].name + <br>                                                   &quot;&lt;/td&gt;&lt;/tr&gt;&quot;;<br>}</pre><p>Now, no matter how many elements are in that array, items.length will only be looked up once.</p><h4>Reduce DOM access</h4><p>The other issue with the example code is the injection action itself. document.getElementById traverses DOM with each loop iteration to find the same listGrid element every time. Instead, let’s just do the lookup once, store the element in a variable and use it in a loop for injection.</p><pre>var itemArrLen = items.length;<br><strong>var element = document.getElementById(&quot;listGrid&quot;);</strong></pre><pre>for (var i = 0; i &lt; itemArrLen; i++) {<br>  <strong>element</strong>.innerHTML += &quot;&lt;tr&gt;&lt;td&gt;&quot; + items[i].name + &quot;&lt;/td&gt;&lt;/tr&gt;&quot;;</pre><pre>}</pre><p>Note, the same applies if you’re using jQuery (or other frameworks) instead of raw HTML for DOM manipulation. Remember, no matter how you to do DOM lookups — you’re still doing DOM lookups.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=aa546a325612" width="1" height="1" alt=""><hr><p><a href="https://medium.com/web-performance-for-developers/reducing-property-access-aa546a325612">Reducing property access</a> was originally published in <a href="https://medium.com/web-performance-for-developers">Web Performance for Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Caching: File system]]></title>
            <link>https://medium.com/web-performance-for-developers/caching-file-system-737155533c5c?source=rss----43285a67f794---4</link>
            <guid isPermaLink="false">https://medium.com/p/737155533c5c</guid>
            <category><![CDATA[application-development]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[caching]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Leon Fayer]]></dc:creator>
            <pubDate>Tue, 21 Nov 2017 21:23:04 GMT</pubDate>
            <atom:updated>2017-11-21T21:23:04.488Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KPaMAtJSvWbRQqPkHgUPow.jpeg" /><figcaption>Toronto, Canada © Leon Fayer</figcaption></figure><p>Perhaps the most effective caching mechanism is in-memory cache. Tools like memcached and redis give a great out-of-the-box capability to store and retrieve your content to/from memory and effectively manage TTL. But what can you do when you don’t have those tools available?</p><p>Never fear, you can implement your own on-disk cache. While not as effective as in-memory cache, it would still provide a significant relief to system under heavy load.</p><h3>The basics</h3><p>The premise is simple. Take any function that retrieves, generates and returns content from any source (such as database queries, API calls and third-party content providers) and add a conditional to it.</p><pre>sub<strong> </strong>generateContent<strong>()</strong> {<br>  if (<strong>has_cache</strong>($cache_file, $cache_ttl)) {<br>    return <strong>_read_cache</strong>($cache_file);<br>  } else {<br>    return <strong>_write_cache</strong>($cache_file);<br>  }<br>}</pre><p>The conditional will check if the cache file exists and if it does, get content from cached file instead. If cached content doesn’t exist, generate content and store it in cache. Simple, right?</p><p><strong>_read_cache </strong>and <strong>_write_cache </strong>functions are as simple as you imagine them to be. It’s a basic file manipulation: open, read/write, close.</p><pre>###### FS caching</pre><pre>sub <strong>_read_cache</strong> {<br>    my $cache_file = shift;<br>    open (my $fh, &#39;&lt;&#39;, $cache_file) <br>      or die &quot;Can&#39;t open file doe reading $!&quot;;<br>    read $fh, my $cache_content, -s $fh;<br>    close $fh;<br>    return $cache_content;<br>}</pre><pre>sub <strong>_write_cache</strong> {<br>    my $cache_file = shift;<br>    my $content = generateContent();</pre><pre>    open(my $fh, &#39;&gt;&#39;, $cache_file)  <br>        or die &quot;Could not open file &#39;$cache_file&#39; $!&quot;;<br>    print $fh $content;<br>    close $fh;<br>    return $cache_content;<br>}</pre><p>The only remaining missing piece is cache validation. Unfortunately, because you’re building a cache system from scratch, unlike in-memory tools that manage your TTL for you, you need to implement your own TTL management. You can simply check the last updated timestamp of the cache file against your desired cached time. Done!</p><pre>sub has_cache {<br>    my $cache_file = shift;<br>    my $cache_ttl = shift; #in seconds</pre><pre>    if (-f $cache_file) {<br>        # in seconds<br>        my $last_updated = (stat($cache_file))[9];<br>        my $now = int (gettimeofday);<br>        my $diff = $now - $last_updated;<br>        return 1 if ($now - $last_updated &lt;= $cache_ttl);<br>    }<br>    return 0;<br>}</pre><h3>The caveat</h3><blockquote>There are only two hard things in Computer Science: cache invalidation and naming things — Phil Karlton</blockquote><p>The advantage of this cache is that it’s persistent, even if you reboot the machine it’s on, the cache files will still be available. The drawback — it’s locally disk-bound. Which, among other things associated with disk bound processes, means you need to have a separate cache on each of the nodes. Which, in turn, means that you need to make sure you invalidate the cache across every node on-change. There are numerous ways of doing it, like setting up listeners on each node or implementing timed or on-demand cache purge instead of on-change triggers. Whichever way you decide to implement it, be mindful of the problem you’re facing. Otherwise you’ll be left with a problem worse than stale content — inconsistent content.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=737155533c5c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/web-performance-for-developers/caching-file-system-737155533c5c">Caching: File system</a> was originally published in <a href="https://medium.com/web-performance-for-developers">Web Performance for Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Caching: Cache-Control header]]></title>
            <link>https://medium.com/web-performance-for-developers/caching-cache-control-header-3d0c6de0b711?source=rss----43285a67f794---4</link>
            <guid isPermaLink="false">https://medium.com/p/3d0c6de0b711</guid>
            <category><![CDATA[application-development]]></category>
            <category><![CDATA[caching]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[web-performance]]></category>
            <dc:creator><![CDATA[Leon Fayer]]></dc:creator>
            <pubDate>Mon, 13 Nov 2017 19:09:21 GMT</pubDate>
            <atom:updated>2017-11-13T21:11:53.080Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4Ldj0d0Kyp4R0OpHDpDCQA.jpeg" /><figcaption>Amazonia, Peru © Leon Fayer</figcaption></figure><p>The easiest way to ensure that your web requests are going to be cached by front end consumers, such as browsers, mobile devices and proxies, is to defined <a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#cache-control">Cache-Control header</a> for all the resources that you serve.</p><p>At the most basic level, all you have to do is add one line to your .htaccess file (if you’re using Apache):</p><pre><em># cache all resources for one month</em><br>Header set Cache-Control “max-age=2628000, public”</pre><p>This will direct front end consumers to, much like the comment says, cache all the pages and files you serve for a month. So if a user hits your webpage and returns to it tomorrow, that request will not hit your server, but rather be served from the browser cache.</p><p>That’s great and all, but chances are, your site is serving dynamic content that changes a little more frequently than once a month. So perhaps you want to put some logic around the caching directives to differentiate the content and TTL for different types of files.</p><pre><em># cache static assets for one month</em><br>&lt;filesMatch “.(jpg|jpeg|png|gif|js|ico)$”&gt;<br>  Header set Cache-Control “max-age=2628000, public”<br>&lt;/filesMatch&gt;</pre><p>In this example, the directive is to cache only images for one months. Other types of files will be served without cache directives.</p><p>You can add more complex rules, such as setting cache directives for individual files and determining TTL on them based on the volatility of the content served.</p><p>You can also define header directly inside your application pages, to add even more granularity and flexibility for offloading content serving on to the consumers. For example, if you’re using PHP, you can add a header directly into an individual page:</p><pre><em># cache this page for one minute</em><br>&lt;? header(“Cache-Control: max-age=60”); ?&gt;</pre><p>And for those thinking that one minute cache is not going to help much, <a href="https://www.slideshare.net/LeonFayer1/developing-applications-for-performance/83">think again</a>!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3d0c6de0b711" width="1" height="1" alt=""><hr><p><a href="https://medium.com/web-performance-for-developers/caching-cache-control-header-3d0c6de0b711">Caching: Cache-Control header</a> was originally published in <a href="https://medium.com/web-performance-for-developers">Web Performance for Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Performance mantras]]></title>
            <link>https://medium.com/web-performance-for-developers/performance-mantras-53f96e1abb4c?source=rss----43285a67f794---4</link>
            <guid isPermaLink="false">https://medium.com/p/53f96e1abb4c</guid>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[application-development]]></category>
            <category><![CDATA[system-architecture]]></category>
            <category><![CDATA[rules]]></category>
            <dc:creator><![CDATA[Leon Fayer]]></dc:creator>
            <pubDate>Fri, 03 Nov 2017 17:07:09 GMT</pubDate>
            <atom:updated>2017-11-13T19:11:30.339Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZtAqSYJV1EEzGF_E2OTTsQ.jpeg" /><figcaption>Lhassa, Tibet © Leon Fayer</figcaption></figure><p>Rules of web application performance</p><h3>Performance != Scalability</h3><p>Those two concepts are not two of the same, even though they are often used in conjunction when talking about web application performance. Best way to think about the difference is to think about the fast food franchises. Scalability is building more restaurants in the neighborhood to serve more people that are moving into the neighborhood. Performance is adding automated ordering system to streamline the process and serve more people in a <em>single location</em>. This is why cloud solves the horizontal scalability problem, but does not, in any way, solve the performance problem.</p><h3>Performance is technology agnostic</h3><p>It doesn’t matter if you’re a Ruby person or prefer Python. Whether you’re still using proven Apache or prefer to keep your code stack in JavaScript and using Node.js. Similarly, if the only database you’re familiar with is MySQL or you’ve had your hands on PostgreSQL, Oracle and even, dare I say, DB2. It doesn’t matter what the technology of choice is, the core principals and techniques to improve the performance are the same across the board. Actual implementation details (config tweaks, syntax, etc.) may slightly vary depending on technology, but the concepts stay true.</p><h3>Performance is not a feature</h3><p>Much like with security, you cannot add “performance” as a line item to your project plan. Performance impact should be considered at every step of design, architecture and development. Because of the cascading nature of performance problems, early omission of performance considerations can cause major outages. Remember, not all the performance problems can even be tested for in development/staging environment. Some can only be <a href="http://fayerplay.com/something-devops/">observed in production</a>.</p><h3>Performance is subjective</h3><p>That said, don’t over engineer for the results you don’t need. Keep in mind the effort vs reward trade off. Optimize for what’s needed and what’s acceptable. And remember:</p><blockquote>Premature optimization is the root of all evil</blockquote><blockquote>— Donald Knuth</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=53f96e1abb4c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/web-performance-for-developers/performance-mantras-53f96e1abb4c">Performance mantras</a> was originally published in <a href="https://medium.com/web-performance-for-developers">Web Performance for Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Migrations: Getting there faster]]></title>
            <link>https://medium.com/web-performance-for-developers/migrations-getting-there-faster-6d0e5a5990d9?source=rss----43285a67f794---4</link>
            <guid isPermaLink="false">https://medium.com/p/6d0e5a5990d9</guid>
            <category><![CDATA[database]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[tips-and-tricks]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[data-migration]]></category>
            <dc:creator><![CDATA[Leon Fayer]]></dc:creator>
            <pubDate>Thu, 02 Nov 2017 02:06:07 GMT</pubDate>
            <atom:updated>2017-12-04T16:23:30.084Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JbkhV21_Mj0_UtgdUmx4PQ.jpeg" /><figcaption>Great Migration, Tanzania © Leon Fayer</figcaption></figure><p>Migration scrips. We’ve all had to write them. They are the background, throwaway scripts that don’t have any performance requirements. Who cares if the script you planned to run overnight takes an extra hour to finish, especially since it doesn’t directly impact users? And generally all of that is true. Of course, when the situation falls out of the “generally” bucket, it fails spectacularly.</p><pre>$max_size = 4200479;</pre><pre>for ($i = 0; $i &lt;= $max_size; $i++) {<br>  <em>// connect to the source database and process row</em><br>  $dbh = new DB(…);<br>  $sth = $dbh-&gt;prepare(&quot;select * from source_table where id = ?”);<br>  $sth-&gt;execute($i);</pre><pre>  _process_row($sth-&gt;fetch());</pre><pre><em>  // connect to the target database and store row</em><br>  $dbh_trgt = new DB(…);<br>  $sth_trgt = $dbh_trgt-&gt;prepare(insert into target_table ... ”);<br>  $sth_trgt-&gt;execute(...);<br>}</pre><p>This is a pretty common base migration script. It iterates through the rows in the old table (4,200,479 of them), modifies the data and structure to fit the new schema and inserts the row into the new table. Now, it has <em>a lot</em><strong><em> </em></strong>of performance issues (<a href="https://medium.com/web-performance-for-developers/n-1-problem-c8911bfd2577">n+1</a> probably being one of the most visible one). But do we care? This is a one time script that will never be used after the migration is complete. Right?</p><p>As it turns out — we do care. The script above would take about <strong><em>6 seconds per row.</em></strong> Which seems decent enough for a throw away. So, to complete the full migration it would take <strong><em>25,202,874 </em>seconds</strong>. Or a little over <strong><em>291 days</em></strong>.</p><p>Yikes.</p><p>So we need to optimize and optimize we shall. Luckily, there are a few simple performance improvement that can be done to reduce the migration runtime from a year to an overnight success.</p><h3>Minimize number of connects</h3><p>Connecting and disconnecting to a database is not free. Many people don’t think about it, but those connections (especially over network) costs resources. It is usually a minimal overhead, but overhead nonetheless. If you connect and disconnect with every query, your overhead will grow in <a href="https://www.slideshare.net/LeonFayer1/developing-applications-for-performance/50">arithmetic progression</a>.</p><p>What you want to do, is to maintain the connection for the duration of the script.</p><pre><em>// connect to source and target databases</em><br>$dbh = new DB(…);<br>$dbh_trgt = new DB(…);</pre><pre>$max_size = 4200479;</pre><pre>for ($i = 0; $i &lt;= $max_size; $i++) {<br>  <em>// get and process row</em><br>  $sth = $dbh-&gt;prepare(&quot;select * from source_table where id = ?”);<br>  $sth-&gt;execute($i);</pre><pre>  _process_row($sth-&gt;fetch());</pre><pre><em>  // store row</em><br>  $sth_trgt = $dbh_trgt-&gt;prepare(insert into target_table ... ”);<br>  $sth_trgt-&gt;execute(...);<br>}</pre><p>Condensing 8,400,958 (4,200,479 x 2 databases) connects and disconnects into 2 makes an immediate impact on overall processing time.</p><h3>Minimize number of queries</h3><p>A standard solution to n+1 problem is to <a href="https://medium.com/web-performance-for-developers/n-1-problem-c8911bfd2577">consolidate all the queries</a> into one (or close to it). Theoretically, that can be done in this migration as well. Instead of selecting individual rows, we can select the whole dataset and then process returned rows in a loop. Unfortunately, returning millions of rows, storing them in memory and processing them at the same time is not good answer. Fortunately, there is a middle ground.</p><p>Instead of returning one row at a time (or all the rows) we can return sets of data in a reasonably sized batches.</p><pre><em>// connect to source and target databases</em><br>$dbh = new DB(…);<br>$dbh_trgt = new DB(…);</pre><pre>$batch_start = 0;<br>$batch_end = 100000;<br>$max_size = 4200479;</pre><pre>while($batch_start &lt;= $max_size) {<br>  <em>// get rows in batch</em><br>  $sth = $dbh-&gt;prepare(&quot;select * from source_table <br>                         where id is between ? and ?”);<br>  $sth-&gt;execute($batch_start, $batch_end);</pre><pre><em>  // increment batch</em><br>  $batch_start = $batch_end+1;<br>  $batch_end += 100000;</pre><pre>  foreach ($row as $sth-&gt;fetchAll()) {<br>    _process_row($row);</pre><pre><em>    // store row</em><br>    $sth_trgt = $dbh_trgt-&gt;prepare(insert into target_table ... ”);<br>    $sth_trgt-&gt;execute(...);<br>  }<br>}</pre><p>The size of batches can vary based on your environment and resource availability. By combining the selected rows into batches of 100,000 we’ve effectively reduced the number of select queries from <em>4+ million</em> to <em>43</em>.</p><h3>Minimize number of transactions</h3><p>To continue the theme of minimizing the number of round trips to the database, reduce the number of insert transactions sent to the database. Note, <em>transactions</em> not <em>queries</em>. Wrapping multiple queries in a transaction allows to submit multiple (read: thousands) queries in one go. This can be accomplished by either turning AutoCommit flag off and managing batch commits manually, or using <a href="https://www.postgresql.org/docs/9.1/static/sql-begin.html">BEGIN</a> block to build own batch transaction.</p><pre><em>// connect to source and target databases</em><br>$dbh = new DB(…);<br>$dbh_trgt = new DB(…);</pre><pre>$batch_start = 0;<br>$batch_end = 100000;<br>$max_size = 4200479;</pre><pre>while($batch_start &lt;= $max_size) {<br>  <em>// get rows in batch</em><br>  $sth = $dbh-&gt;prepare(&quot;select * from source_table <br>                         where id is between ? and ?”);<br>  $sth-&gt;execute($batch_start, $batch_end);</pre><pre><em>  // increment batch</em><br>  $batch_start = $batch_end+1;<br>  $batch_end += 100000;</pre><pre><em>  // begin transaction</em><br>  $dbh-&gt;beginTransaction();</pre><pre>  foreach ($row as $sth-&gt;fetchAll()) {<br>    _process_row($row);</pre><pre><em>    // store row</em><br>    $sth_trgt = $dbh_trgt-&gt;prepare(insert into target_table ... ”);<br>    $sth_trgt-&gt;execute(...);<br>  }</pre><pre><em>  // commit transaction<br></em>  $dbh-&gt;commit();<br>}</pre><p>Adding all inserts for the batch into a single transaction would send all the queries to the database at the same time to execute and commit. So instead of 100,000 commits going to the database, you only send the transaction with all the insert statements and commit once, also effectively reducing a total number of insert transactions for the migration from <em>4+ million</em> to <em>43</em>.</p><h3><strong>Maximize parallelism</strong></h3><p>If database is the biggest offender in the 6 second processing time, the techniques above would likely give the script a significant enough boost in performance to complete in a reasonable timeframe. However, if the increase in performance on the database side is not enough or the bottleneck is in the _process_row() function — you can <em>fork</em> your script or run multiple instances of the script in parallel, processing multiple batches at the same time.</p><p>If transactions and batching are implemented properly, you can fork multiple processes inserting millions of records at the same time in parallel. This, of course, if limited by CPU (don’t fork bomb the machine!), but even with small number of parallel processes you’re significantly reducing time to completion.</p><pre><em>// connect to source and target databases</em><br>$dbh = new DB(…);<br>$dbh_trgt = new DB(…);</pre><pre>$batch_start = 0;<br>$batch_end = 100000;<br>$max_size = 4200479;</pre><pre>while($batch_start &lt;= $max_size) {</pre><pre>  //Fork a process<br>  $pid = pcntl_fork();</pre><pre>  //if a child - process a batch<br>  if (!$pid) {<br><br>  <em>  // get rows in batch</em><br>    $sth = $dbh-&gt;prepare(&quot;select * from source_table <br>                         where id is between ? and ?”);<br>    $sth-&gt;execute($batch_start, $batch_end);</pre><pre><em>    // increment batch</em><br>    $batch_start = $batch_end+1;<br>    $batch_end += 100000;</pre><pre><em>    // begin transaction</em><br>    $dbh-&gt;beginTransaction();</pre><pre>    foreach ($row as $sth-&gt;fetchAll()) {<br>      _process_row($row);</pre><pre><em>      // store row</em><br>      $sth_trgt = $dbh_trgt-&gt;prepare(insert into target_table .. ”);<br>      $sth_trgt-&gt;execute(...);<br>    }</pre><pre><em>    // commit transaction<br></em>    $dbh-&gt;commit();</pre><pre>    //Die to exit a child process<br>    die();</pre><pre>  }<br>}</pre><p>This final version of the migration script will spawn 43 children to run batches of 100,000 records in parallel, finishing in minutes/hours (depending on the processing time and complexity of queries). You can control both the size of batches and the number of parallel processes that best match the environment you’re running it on.</p><p>There are always additional performance improvements that could be done, but for most throwaway scripts these techniques should be enough to bring the migration time to a much more reasonable (read: acceptable) level.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6d0e5a5990d9" width="1" height="1" alt=""><hr><p><a href="https://medium.com/web-performance-for-developers/migrations-getting-there-faster-6d0e5a5990d9">Migrations: Getting there faster</a> was originally published in <a href="https://medium.com/web-performance-for-developers">Web Performance for Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Queueing: Optimizing for critical path]]></title>
            <link>https://medium.com/web-performance-for-developers/queueing-optimizing-for-critical-path-de755d3eca0e?source=rss----43285a67f794---4</link>
            <guid isPermaLink="false">https://medium.com/p/de755d3eca0e</guid>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[tips-and-tricks]]></category>
            <category><![CDATA[web-app-development]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Leon Fayer]]></dc:creator>
            <pubDate>Mon, 30 Oct 2017 20:26:00 GMT</pubDate>
            <atom:updated>2017-10-30T20:27:02.111Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*3EnOnUkNMV3VQ2tWQ0phkA.jpeg" /></figure><p>One of the biggest performance killers happen to be the non-essential actions that are being executed on a critical path of your application. Let’s examine a simple registration example:</p><pre>router.post(&#39;/<em>register</em>&#39;, function(req, res){<br>    var user = new User(req.body);<br>    user.save( function(err) {<br>        if (!err) {<br>            user.savePreferences(req.pref);<br>            user.sendWelcomeEmail();<br>            res.render(‘<em>first_time_welcome</em>&#39;,<br>              { title: &#39;<em>Welcome</em>&#39; + user.name, errors: [err] }<br>            );<br>        } <br>    });<br>});</pre><p>The code looks pretty straight forward. User submits the registration form, the code saves used details and preferences, sends out a welcome email and shows the user a “welcome” page.</p><p>Optimizing for user experience — you want to display the confirmation page as soon as possible, so the user can start shopping/posting/doing whatever is most beneficial to your business. As such, you want to make sure that the registration process is fast and painless. So let’s time it and make sure that it is. On average, the code above returned the desired “welcome” page in <em>2.7 seconds, </em>with occasional spikes taking up to <em>10 seconds</em>. Not exactly within acceptable performance range, especially since the code is pretty slim, doing two quick database inserts (for user.save and user.savePreferences) and is not doing any heavy logical computation.</p><p>Adding timing to individual actions, it appears that the function that takes up the most cycles is user.sendWelcomeEmail.</p><pre>User.sendWelcomeEmail = function(callback){<br>  if (!do_not_email(user.email) {<br>    var email = new Email();<br>    email.subject = &quot;<em>Welcome</em> &quot; + user.firstname;<br>    email.cta = getDealoftheDay();<br>    email.secondary = getOffers(user);<br>    email.generateBody(user, function() {<br>      email.send();<br>    });<br>  }<br>};</pre><p>Bingo! user.sendWelcomeEmail retrieves information from the database, does real-time targeting, and invokes to be sent out immediately. There are likely some optimizations that can be done to the code to speed up the process (with caching, pre-processing and other techniques), but the real question that any developer should be asking themselves is — <em>why are we sending a welcome email during the registration process</em>?</p><h3>Does it have to be done <em>now</em>?</h3><p>To answer this question you have to understand what the word “now” means to your business. Should the welcome email be sent to a new user upon registration? Yes. Should the welcome email be sent <em>before</em> user is shown a “welcome” page? Probably not.</p><blockquote>Anything not pertinent to the <em>immediate business</em> <em>outcome</em> should not be in the critical path</blockquote><p>The immediate goal is to take user into the site . Welcome email, while maybe be just as valuable from the business return perspective, is a secondary goal based on timing. Which means user.sendWelcomeEmail function does not belong in the registration process. And this applies to any other functionality that does not assist in accomplishing the primary goal. Anything not pertinent to the <em>immediate business</em> <em>outcome</em> should not be in the critical path. Which means that all the other processes need to be moved outside of the critical path.</p><h3>Enter queuing</h3><p>Queueing is also a good way to queue up a number of actions that can be processed at a pace slower than realtime user experience. The application would add an entry to your queue and external job would pick up those entries in the order of appearance (FIFO).</p><p>The are a number of ways (and tools) to implement a queue, but most commonly used ones are MQ-specific tools and databases.</p><h4>Message Queue Tools</h4><p>There are tools available to use as a basis for your queue. If you’re reliant on Open Source tools, <a href="https://www.rabbitmq.com">RabbitMQ</a> is probably the most popular and easy to set up tool. If you’re heavily invested into Amazon ecosystem, <a href="https://aws.amazon.com/sqs/">SQS</a> may be something to look into.</p><h4>Database</h4><p>You can also implement your own queue using any database. Traditionally, you would create a queue table and have a cronjob pull the new data every so often (based on your latency requirements) and process it. Additionally, some of the modern databases implement <a href="https://omniti.com/seeds/stop-collaborate-and-listen-notify">queueing functionality</a>, allowing for jobs to subscribe to your queue table and wait for new entries, instead of traditional periodic pulls.</p><h3>The right way</h3><p>Going back to the registration code, it would require a minor change to the flow.</p><pre>router.post(&#39;/<em>register</em>&#39;, function(req, res){<br>    var user = new User(req.body);<br>    user.save( function(err) {<br>        if (!err) {<br>            user.savePreferences(req.pref);<br>            <strong>sendToQueue(</strong><em>‘welcome email’</em><strong>,</strong>user<strong>);</strong><br>            res.render(‘<em>first_time_welcome</em>&#39;,<br>              { title: &#39;<em>Welcome</em>&#39; + user.name, errors: [err] }<br>            );<br>        } <br>    });<br>});</pre><p>Additionally, your cronjob just need to iterate through the new entries in queue and call the same user.sendWelcomeEmail function that was previously in the critical path of our registration process.</p><pre>while (var user = readFromQueue(&#39;<em>welcome email</em>&#39;)) {<br>   user.sendWelcomeEmail();<br>}</pre><p>Now, the welcome emails goes out within 1 to 5 minutes of user registration, which is an acceptable latency, given that the time to “welcome” page has been cut down to <em>300 milliseconds.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=de755d3eca0e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/web-performance-for-developers/queueing-optimizing-for-critical-path-de755d3eca0e">Queueing: Optimizing for critical path</a> was originally published in <a href="https://medium.com/web-performance-for-developers">Web Performance for Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>