Saturday, August 1, 2015

High Performance Web Sites

Chapter A, The Importance of Frontend Performance

"Only 10–20% of the end user response time is spent downloading the HTML document. The other 80–90% is spent downloading all the components in the page"

"This book offers precise guidelines for reducing that 80–90% of end user response time"

Chapter B, HTTP overview

1- Compression:
  • HTTP Request: Accept-Encoding: gzip,deflate
  • HTTP Response: Content-Encoding: gzip
2- Conditional Get Request: 
  • If the browser has a copy of the component in its cache, but isn’t sure whether it’s still valid, a conditional GET request is made. ( basically the browser is not sure if it is still valid because the component doesn't have Expires header)
  • the browser sends a GET request with "If-Modified-Since: Wed, 22 Feb 2006 04:15:54 GMT", the server responds "304 Not Modified" with "Last-Modified: Wed, 22 Feb 2006 04:15:54 GMT" header.
  • if the content has modified, the server responds "200 OK with content".
  • ETagIf-None-Match headers are other ways for Conditional GET, will talk about them later.
3- Expires:
  • as mentioned before, the browser sends Conditional GET because the component doesn't have Expires header, add Expires header to save this round trip.
4- Keep-Alive:
  • each HTTP request required opening a new socket connection (too much)
  •  browsers can make multiple requests over a single connection by using Connection: keep-alive header. The server also responds with Connection: keep-alive.
  • The browser or server can close the connection by sending a Connection: close header.
  • in old browsers, a browser sends a request, wait for the response and then sends another request, Pipelining has been defined in HTTP/1.1 which allows for sending multiple requests over a single socket without waiting for a response. ( better performance, not supported in old Browsers.

Chapter 1, Rule 1: Make Fewer HTTP Requests

Make fewer HTTP request by:

1- Image Maps: 

There are drawbacks to using image maps.Defining the area coordinates of the image map, if done manually, is tedious and error-prone

2- CSS Sprites: 
CSS sprites combine multiple images into a single image, an example of such an image:

To use an image from the sprite:


3- Inline Images:

<IMG ALT="Red Star"
SRC="data:image/gif;base64,R0lGODlhDAAMALMLAPN8ffBiYvWW
lvrKy/FvcPewsO9VVfajo+w6O/zl5estLv/8/AAAAAAAAAAAAAAAACH5BAEA
AAsALAAAAAAMAAwAAAQzcElZyryTEHyTUgknHd9xGV+qKsYirKkwDYiKDBia
tt2H1KBLQRFIJAIKywRgmhwAIlEEADs=">

URL scheme is not supported in Internet Explorer (up to and including version 7), there is a limitation on the size, sure it is not cached.

4- Combined Scripts and Stylesheets
Rather than having multiple CSS files, combine them in one file, same thing for Scripts files. Sure the idea is against writing a modular code, combine these files in the build process.


Chapter 2, Rule 2: Use a Content Delivery Network

That is it, use CDN to improve static content delivery, the writer hasn't talked much about hosting Dynamic content on CDN.
One experience i had before with Akamai was to host content that were available only for Authorized & Authenticated users, and actually helped us caching this type of content efficiently; Here is an article that talk about that
http://www.akamai.co.jp/enja/dl/feature_sheets/FS_edgesuite_accesscontrol.pdf 


Chapter 3, Rule 3: Add an Expires Header

  1. Use the Expires header so the browser doesnt have to go to the server to fetch unexpired content
    Expires: Thu, 15 Apr 2020 20:00:00 GMT
  2. Because the Expires header uses a specific date, it has stricter clock synchronization requirements between server and client, that is why a new header has introduced
  3. Cache-Control:max-age has introduced to solve this limitation which take the expiration value in seconds, Cache-Control: max-age=315360000
  4. If both, Expires and Cache-Control max-age, are present, the HTTP specification dictates that the max-age directive will override the Expires header
  5. If we configure components to be cached by browsers and proxies, how do users get updates when those components change? To ensure users get the latest version of a component, change the component’s filename in all of your HTML pages. (another solution is to add a query string with a version number xxx.js?v=123 and update the version, however i found that developers complain that browsers sometimes ignore the query string when it comes to caching, so the safest option is to update the file name)

CHAPTER 4,Rule 4: Gzip Components

Use compression to reduce the size of the response
  • Client sends: Accept-Encoding: gzip, deflate
  • Server compresses the response using one of the accepted methods and reply
    Content-Encoding: gzip
  • There is a cost to gzipping: it takes additional CPU cycles on the server to carry out the compression and on the client to decompress the gzipped file
  • Image and PDF files should not be gzipped because they are already compressed.
  • Generally, it’s worth gzipping any file greater than 1 or 2K
  • Apache 1.3 uses mod_gzip for compressing while Apache 2.x uses mod_deflate
Proxy Caching and Compressing
Imagine the following scenario:
  1. The first request to the proxy for a certain URL comes from a browser that does not support gzip ( so the request doesn't have Accept-Encoding: gzip, deflate ).
  2. the proxy cache is empty
  3. The proxy forwards that request to the web server
  4. The web server’s response is uncompressed ( because the request doesn't have Accept-Encoding: gzip, deflate ).
  5. The response will be cached by the proxy and sent on to the browser
  6. Now, suppose the second request to the proxy for the same URL comes from a browser that does support gzip
  7. The proxy responds with the (uncompressed) contents in its cache, the second request missed  the opportunity to get compressed content.
Now imagine this scenario, the first request is from a browser that supports gzip and the second request is from a browser that doesn’t. In this case, the proxy has a compressed version of the contents in its cache and serves that version to all subsequent browsers whether they support gzip or not.

To solve this problem:
  1. the Web server should tell the Proxy server to save multiple cached responses of the same URL. This happens by using the Vary header in the response (e.g. Vary: Accept-Encoding), this causes the proxy to cache multiple versions of the response, one for each value of the Accept-Encoding request header.
  2. You can prevent Proxy server from keeping a cached copy by setting Cache-Control: private in the response.






CHAPTER 5, Rule 5: Put Stylesheets at the Top

  1. we want the browser to display whatever content it has as soon as possible (load progressively)
  2. putting stylesheets near the bottom of the document prohibits progressive rendering in many browsers as components are (in general) downloaded in the order in which they appear in the document.
  3. Browsers block rendering to avoid having to redraw elements of the page if their styles change (which means, browsers wait until all stylesheets are loaded before calculating the style of the loaded elments).
So basically the Rule is : Put your stylesheets in the document HEAD using the LINK tag.


CHAPTER 6, Rule 6: Put Scripts at the Bottom

  1. Experiment: put a script in the middle of a page, programm the script to take 10 seconds to load, you will notice the problem which is the bottom half of the page takes about 10 seconds to appear.
  2. As we mentioned before, when using stylesheets, progressive rendering is blocked until all stylesheets have been downloaded.That’s why it’s best to move stylesheets to the document HEAD, so they are downloaded first and rendering isn’t blocked. However, with scripts, progressive rendering is blocked for all content below the script.Moving scripts lower in the page means more content is rendered progressively.
Parallel download
  1.  HTTP/1.1 specification suggests that browsers download two components in parallel per hostname, so if you are downloading from the same hostname you will see something like this


    However, if you are downloading from 2 hostnames, you will find 4 parallel download bars,
  2. Note that for HTTP/1.0, Firefox’s default is to download eight components on parrallel, you can change these value in the browser configuration.
  3. To increase parallel downloaded components, use CNAMEs (DNS aliases) to split their components across multiple hostnames.
  4. Too many parallel downloads can degrade performance.Research at Yahoo! shows that splitting components across two hostnames leads to better performance than using 1, 4, or 10 hostnames
Scripts Block Downloads
  1. Parallel downloading is disabled while a script is downloading—the browser won’t start any other downloads, even on different hostnames
  2. This is to guarantee that the scripts are executed in the proper order.If multiple scripts were downloaded in parallel, there’s no guarantee the responses would arrive in the order specified.
  3. Also because the script may use document.write to alter the page content, so the browser waits to make sure the page is laid out appropriately.
So if we put the scripts at the top (this is the worst case):
  1. Content below the script is blocked from rendering.
  2. Components below the script are blocked from being downloaded.
But if we put the scripts at the bottom (this is the best case):
  1. The page contents aren’t blocked from rendering
  2. Viewable components in the page are downloaded as early as possible
That's why we should Move scripts to the bottom of the page.


CHAPTER 7, Rule 7: Avoid CSS Expressions

  1. This rule is only for IE browsers as CSS expressions are not available in other browsers.
  2. Example of such an expression:
    background-color: expression( (new Date()).getHours( )%2 ? "#B8D4FF" : "#F08A00" );
    JavaScritpt is used to write an expression which makes the background color alternates every hour.
  3. CSS expressions are evaluated more than what we expect, they are reevaluated for various events including resize, scrolling, and mouse movements.
  4. This may cause a performance issue.
  5. The author mentioned a way to overcome this issue, but i believe it is better not to use CSS expression at all as the rule says.

CHAPTER 8, Rule 8: Make JavaScript and CSS External

  1. The title advises to make the JS and CSS external not internal. However, this chapter introduces many advice.
  2. In general, when the JS and CSS are external, you will get the benefit of browser's cache. On the other side, inline JS and CSS will be loaded faster if there is no cache in the browser (i.e. first visit to page).
  3. Think about combining all CSS in one file and all JS in one file.  This has the benefit of subjecting the user to only one HTTP request, but it increases the amount of data downloaded on a user’s first page view.
  4. Think about categorizing your pages into a handful of page types and then create a single script and stylesheet for each one.
  5. some websites' homepages are not used frequently, so having JS and CSS embedded internally could be a good idea ( remember that the browser deletes the long time unused cached content even if it is not expired).
Post-Onload download
  1. In some critical pages, you can embed the JS and CSS files internally, and add a javascript function to download them. 
  2. By that, the first page access will be served by the internal JS and CSS, the subsequent access will be served by the cached downloaded JS and CSS.
  3. there is an example of such a function in the book.

Dynamic Inlining
  1. Another idea, as we don't know what is stored in the browser cache, we can use a cookie as an indicator. 
  2. If the cookie is absent, the JavaScript or CSS is inlined. If the cookie is present, it’s likely the external component is in the browser’s cache and external files are used.
  3. On the first page visit, there will be no cookie, JS and CSS will be inlined and the cookie will be set.
  4. In the subsequent requests, the cookie will be there so the JS and CSS will be rendered as external links and will be served by the browser cache.

CHAPTER 9, Rule 9: Reduce DNS Lookups


  1. DNS resolver has a cost. It typically takes 20–120 milliseconds for the browser to look up the IP address for a given hostname.
  2. The browser can’t download anything from this hostname until the DNS lookup is completed.
  3. DNS lookups are cached in different locations for better performance:
    • on a special caching server maintained by the user’s ISP.
    • local area network.
    • in the operating system’s DNS cache (the “DNS Client service” on Microsoft Windows).
    • browsers own caches.
Factors Affecting DNS Caching
  1. The DNS record returned from a lookup contains a time-to-live (TTL) value. This tells the client how long the record can be cached.
  2. Operating system caches respect the TTL, 
  3. Browsers often ignore it and set their own time limits.
  4. The Keep-Alive feature of the HTTP protocol, can override both the TTL and the browser’s time limit (i.e. as long as the browser and the web server are communicating and keeping their TCP connection open, there’s no reason for a DNS lookup).
  5. Browsers put a limit on the number of DNS records cached (i.e. earlier DNS records are discarded).
  6. If the browser doesn't have a DNS record, the operating system cache will be checked, if it is not there, the local area network or the ISP cache will be checked.
TTL Values
  1. When the browser does a DNS lookup, the DNS resolver returns the amount of time remaining in the TTL for its record. (that is because the DNS entry has already lived for an amount of time in this DNS resolver).
  2. For example, if the maximum TTL is 5 minutes, the TTL returned by the DNS resolver ranges from 1 to 300 seconds.
DNS From OS and Browser’s Perspective
  1. The DNS cache on Microsoft Windows is managed by the DNS Client service
    • to view the cache : ipconfig /displaydns
    • to fulsh: ipconfig /flushdns
    • Rebooting clears the DNS Client service cache
  2. Internet Explorer’s DNS cache is controlled by three registry settings:
    • These settings created in the registry key:
      HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\InternetSettings\
    • DnsCacheTimeout: 30 minutes (i.e. if IE received a TTL value less than 30 minutes from the server, it will be ignored).
    • KeepAliveTimeout: 1 minute (i.e.  a persistent TCP connection is used until it has been idle for one minute, during this 1 minute no DNS lookups will be happened).
    • ServerInfoTimeOut: 2 minutes (i.e. even without Keep-Alive, if a hostname is reused every two minutes without failure, a DNS lookup is not required).
  3. Firefox  has the following configuration settings:
    • network.dnsCacheExpiration: 1 minute.
    • network.dnsCacheEntries: 20 (this value is too small).
    • network.http.keep-alive.timeout: 5 minutes.
  4. FasterFox ( Firefox add-on for measuring and improving Firefox performance)
    1. network.dnsCacheExpiration: 1 hour.
    2. network.dnsCacheEntries: 512.
    3. network.http.keep-alive.timeout: 30 seconds.
Notes


  1. Reducing the number of unique hostnames in the page reduces the number of DNS lookups (this is true only if the client DNS cache is empty).
  2. However, reducing the number of unique hostnames has the potential to reduce the amount of parallel downloading.
  3. for a good compromise between reducing DNS lookups and allowing a high degree of parallel downloads, the author suggests to split the components across at least two but no more than four hostnames.
  4. remember that using Keep-Alive reduced DNS look-ups.
CHAPTER 10, Rule 10: Minify JavaScript

Minification
  1. Minification is  removing unnecessary characters from code to reduce its size, thereby improving load times.
  2. When code is minified, all comments are removed, as well as unneeded whitespace characters (space, newline, and tab).
  3. JSMin is good tool for minification.
  4. minification is good for external as well as internal scripts.
  5. sure, you can also minify CSS files.
Obfuscation 
  1. Like minification, it removes comments and whitespace.
  2. It also munges the code, function and variable names are converted into smaller strings making the code more compact, as well as harder to read.
  3. Make the code difficult to reverse-engineer.
  4. Because obfuscation is complex, it makes the code hard to maintain and debug, and it may introduce bugs.
  5. ShrinkSafe is a good tool for obfuscation.
  6. Minification is preferred over obfuscation.


Chapter 11, Rule 11: Avoid Redirects

Types of Redirects

  1.  300 Multiple Choices: 
    • The server has multiple representations of the requested resource.
    • The client didn’t use the Accept-* headers to specify a representation, or it asked for a representation that doesn’t exist.
    • The server can pick its preferred representation, and send it with a 200 (“OK”) status code. or send a 300 response with a list of possible URIs to different representations.
    • If the server has a preferred representation, the server can put the representation URI in the Location header.
    • If the server needs to return a list of representations, the server uses the response body.
  2. 301 Moved Permanently:
    • The server knows which resource the client is trying to access.
    • The server wants to tell the client to stop using this URI to access this resource and use a different URI.
    • the server sends 301 response with the new URI in the Location header. The client should make a note and stop using the old URI.
  3. 303 See Other 
    • Avilable only in HTTP/1.1.
    • 303 means that the request has been processed, but the server will not send a response document. The server will send the client a new URI (in the Location header) which points to a response document.
    • if the client wants to download the response document, they can send a GET request to the new URI. (very important, the client always send a GET request).
    • Example: the client request for http://www.example.com/software/BuildPdfDocument, the server replies with 303 and http://www.example.com/software/DownloadPdfDocument?id=123. Which means the server has built the pdf document and is telling the client that they can download it from the new link if they want.
  4. 307 Temporary Redirect
    • Avialble only in HTTP/1.1
    • 307 means that the request has not been processed, the client should resubmit the request to another URI (very important, if the first request was POST, DELETE, PUT the client should do the same request to the new URI not like 303 where the client should always send a GET request).
  5. 302 Moved Temporally (a.k.a Found)
    • 303 & 307 came to solve the ambiguity of this response.
    • 302 should be used like 307 response.
  6. 304 Not Modified:
    • This is not a redirect.
    • The client asks for a resource with If-Modified-Since header.
    • The server replies 304 if the resource hasn't been modified.
  7. 305 Use Proxy: Not important 
Notes
  1. The 301 and 302 status codes are the ones used most often.
  2. Neither a 301 nor a 302 response is cached in practice unless additional headers, such as Expires or Cache-Control, indicate that it should be.
  3. other redirect mechanism is the HTML meta refresh tag
    <meta http-equiv="refresh" content="0"; url="http://google.com">
  4. JavaScript can be used to redirect users.
  5. it is recommended to use HTTP redirect.
  6. You may have issues with the browser back button if you use JavaScript redirect (window.location.replace vs window.location.assign).
How Redirects Hurt Performance
  1. The author describes the fact that the requested HTML document will not be downloaded until the redirect is done. Moreover, stylesheets and Scripts will not be downloaded until the HTML document is downloaded. If we do too many redirects, the user will not see anything on the screen until the redirect is done.
  2. The author advises to find other ways to solve issues that could be solved by redirection.

CHAPTER 12, Rule 12: Remove Duplicate Scripts

  1. Duplicate scripts hurt performance: unnecessary HTTP requests and wasted JavaScript execution.
  2. Make sure scripts are included only once.
  3. One way could be by using a script to check duplication. Rather than using
    <script type="text/javascript" src="asdf.js"/>
    to include a script, programmers can use.
    <?php insertScript("asdf.js")?>
  4. insertScript will check if asdf.js is inserted before or not, it also check if it has other dependencies so it can insert them.

<?php
function insertScript($jsfile) {
       if ( alreadyInserted($jsfile) ) {
            return;
      }
      pushInserted($jsfile);
      if ( hasDependencies($jsfile) ) {
          $dependencies = getDependencies($jsfile);
          Foreach ($dependencies as $script) {
               insertScript($script);
          }
      }
     echo '<script type="text/javascript" src="' . getVersion($jsfile) . '"></script>";
}
?>


CHAPTER 13, Rule 13: Configure ETags

ETags are a mechanism that web servers and browsers use to validate cached components.


Conditional GET Requests
  1. When a cached component does expire (or the user explicitly reloads the page), the browser can’t reuse it without first checking that it is still valid.
  2. The browser sends a Conditional Get Request to server to check if the cached version is still valid. 
  3. The server will reply “304 Not Modified” if the cached version is still up to date or "200 OK" with the new version of the content if the cached version has been modified.
  4. There are two ways in which the server determines whether the cached component matches the one on the origin server:
    1. By comparing the last-modified date
    2. By comparing the entity tag
Last-Modified Date
  1. The client sends a get request.
  2. The server replies with Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT
  3. When the component is expired, the client sends a get request with If-Modified-Since: Tue, 12 Dec 2006 03:03:59 GMT
  4. The server replies "304 Not Modified", if the content has not been modified.

Entity Tags
  1. ETags were added to provide a more flexible mechanism for validating entities than the last-modified date.If, for example, an entity changes based on permissions, the User-Agent or Accept-Language headers.
  2. The client sends a get request.
  3. The server replies with ETag: "10c24bc-4ab-457e1c1f".
  4. When the component is expired, the client send a get request with If-None-Match: "10c24bc-4ab-457e1c1f".
  5. The server replies 304 Not Modified, if the content has not been modified.
The Problem with ETags
  1. They are constructed using attributes that make them unique to a specific server hosting a site (i.e. in case of a cluster of servers, ETags won’t match when a browser gets the original component from one server and later makes a conditional GET request that goes to a different server). which means unnecessary reloading of components.
  2. Apache adds information like file type, owner, group, and access mode to the ETags.
  3. IIS uses different information.
  4. If both If-None-Match and If-Modified-Since are in the request, the origin server “MUST NOT return a response status of 304 (Not Modified) unless both conditions met.
What to do 
  1. If you have components that have to be validated based on something other than the last-modified date, ETags are a powerful way of doing that, 
  2. In case of a single server website, you can let the web server (e.g. Apache) to generate ETags for you
  3. In case of cluster of servers make sure to configure that ETag header by yourself, dont let the webserver to do that.



No comments:

Post a Comment