In the recent survey of the performance of betting sites during the Grand National 2015 by an independent industry expert, the bet365 mobile site came #1 in sector for performance. The survey included detailed analysis of multiple performance metrics across a panel of the leading 12 betting sites in the UK in the lead up to and during the biggest race of the year. The metrics used were Time To Bet, Transactions By Step, Limited Bandwidth, Total Transaction Weights, and Transaction Payload versus Performance, Page Errors and Quality of Service.This is the first in a series of articles where I will discuss some of the approaches we used to achieve our sector leading performance.
The key items to always consider when optimising websites for mobile devices are:
- Network
- Memory
- Battery Life
In this article I will focus on Network, and the techniques and strategies we used to ensure that our mobile site used our customers’ internet connection as efficiently as possible.
A journey of a thousand miles begins with a single step
Chinese Philosopher Lao-Tze in Tao Te Ching, 6th Century BC
Network
Although the number of unlimited data plans has increased during the last few years, most users still have a monthly data usage limit. This means that they are paying for every byte sent over the internet. Even in cases where data usage is effectively unlimited, payload size has a direct impact on the speed and performance of the site for every user. These points are critical when considering network performance:
- Keep 3rd Party Requests to an absolute minimum, especially when they would block rendering
- Identify “Must Have” assets and ensure they are as small as possible and loaded as early as possible
- Lazy load any assets that are not “must haves” at the time you need them (but only once!)
- Combine assets where possible to reduce the number of HTTP requests
- Minify assets
- Use HTTP compression techniques (GZIP, DEFLATE)
- Cache assets for as long as possible
3rd Party Requests
3rd party request are requests for content that are hosted on completely different domains to your primary domain, operated by other organisations that you have no direct control over. These are typically requests for scripts that perform a specific task such as site analytics or user tracking.
Each 3rd party request hands some part of your page load time over to someone else, and so you place yourself at the mercy of their infrastructure. There are also concerns over the possibility of Cross Site Scripting (XSS) vulnerabilities if the 3rd party is compromised.
For these reasons we endeavour to keep 3rd party requests to an absolute minimum, ideally none.
“Must Have” Assets
These are items that are essential to the functioning of the site in all cases. These should be as far as possible baked-in to the initial page request in order to reduce the number of subsequent requests the browser needs to make to load and render the page. Some approaches include:
- Use inline CSS where feasible
- Use inline scripts where feasible
- Using Data URI’s for images
- Use the <script> tags defer and async attributes
<script> defer and async
The <script> tag provides different options for loading external scripts (when specifying the src attribute).
defer indicates that the script execution can be delayed until the HTML parser has finished processing the mark-up. This means that while the browser is downloading the script file it can still continue to process and render the web page. The browser will then execute the script once the page loading is complete.
async indicates that the downloading of the script should not block the HTML parser from processing the mark-up. However, the script block will be executed as soon as it is downloaded, not necessarily when the page is fully loaded.
Lazy Loading
Lazy loading is critical to keeping payload weight to a minimum. It is very unlikely that every customer interacts with every feature, so they should not be forced to download assets that they will never need or code they will never execute.
By using lazy loading, we can ensure that the code we load into the user’s browser is delivered on-demand. This does have the negative drawback of slowing down the first use of a particular feature, but we ensure that any resources lazy-loaded are cached so that there are no subsequent performance hits. Indeed, by using caching appropriately we can ensure that the benefit of having the assets cached persists across browser sessions, but more on caching later.
Combining
Every HTTP request incurs its own overhead due to the HTTP headers attached to every request and response. For example, if your site has a lot of cookie data this will always be passed in every request from the client to the server. Therefore reducing the number of HTTP request that the site needs to make will reduce this overhead.
Taking individual files and concatenating the contents together to produce a single output file is the combining process. Some assets lend themselves well to combining – typically JavaScript and CSS.
However, an important consideration when combining is that combined files will download serially over a single TCP stream, whereas separate files will be able to download in parallel over multiple TCP streams. This may not be desirable in the case of assets that are not necessarily dependant on each other and do not need to be loaded in a specific order, and can be utilised immediately once downloaded.
Minifying
Minification of JavaScript and CSS has become the de-facto minimum requirement for large scale websites. Minified JavaScript has become the assembly language of the web – almost unreadable by humans and optimised for delivery to the browser in order to ensure that the minimum number of bytes is delivered to provide the desired result.
Minification tools have progressed greatly in recent years. Tools such as Google’s Closure compiler provide high levels of minification by performing code analysis to determine dependencies.
We use a pipeline of tools to minify our scripts, which allows us to group and combine scripts together in order to get the maximum performance we can out of our minifier of choice. At present this is Microsoft’s AjaxMin. It integrates well with our existing build process, and has useful features such as C Style Pre-Processor Macros that allow us to ensure that debug or test code does not get into production. Most importantly it also produces minified code output that compares favourably with other minifiers.
HTTP Compression
Support for gzip compression has become almost completely ubiquitous in both browser and server. There are now very few reasons to not apply gzip compression, but it is important to consider that there are diminishing returns from gzip compression on smaller response sizes. There is even a threshold at which applying gzip compression actually increases the size of the response due to the overhead of the compression data added in order to decompress the response.
Typical rules of thumb are:
- Below 150 bytes the size of the data will most likely increase when applying compression
- Below 800 bytes the data will typically fit inside a single TCP packet, compressed or not (depending on the size of HTTP headers returned with the response)
- Above 1000 bytes the data will almost certainly benefit from compression
However, your mileage may vary depending on the nature of the responses.
As with any set of rules, there are exceptions. Data that already has some form of compression applied to it is unlikely to benefit from applying it further. Data formats such as PNG, JPEG, MPEG/MP3/MP4 which are compression formats in themselves should in most cases not be compressed further.
Another point of note which can often be forgotten is that compression only applies to responses, not requests. If you are POSTing a large payload (for example JSON data) back to your web server this will not be sent compressed, and neither will HTTP Cookie payloads which will be attached by the browser to every request. It may be worth considering whether you need to send the data at all. If you do, perhaps look at using custom compression code in the browser to shrink the data, and handle decompressing the data in the web server for those requests.
Caching
The fastest HTTP request is one that you don’t have to make. Configuring HTTP headers on responses for static content allows the browser to serve those requests from its own cache. Even if the browser does not have the item in its cache (on first request for example), appropriate caching headers can instruct intermediate systems such as proxy and caching servers to cache the response for all users, resulting in faster response times and less load on your web servers.
The Cache-Control HTTP Header provides a quite granular control mechanism, and permits control over browser cache and intermediate cache lifetimes. Specifying “public” means that intermediate caches are permitted to cache the response, “max-age” is the time in seconds that the response can be cached for. If present, the “max-age” directive takes precedence over the Expires header, even if the Expires header is more restrictive.
Browser caching for content served over HTTPS was at one time considered unnecessary. However, modern browsers now cache HTTPS content as readily as any other content. In order to ensure that HTTPS responses are not cached in the browser, it is necessary to set the Cache-Control header to no-store.
Summary
Carefully consider every HTTP request that your site makes. Ask yourself the following questions:
- “Is this necessary?” – Remove redundancy
- “Do I need it now?” – Lazy load
- “Can I make this smaller and faster?” – Optimise, Minify, Compress
- “Can I bundle this with something else?” – Combining
- “Can I save this for later?” – Leverage Browser and Network Caching
Further Reading
Google PageSpeed Insights Rules
HTTP Header Cache-Control
Web Debugging Proxy tools:
http://www.telerik.com/fiddler
http://www.charlesproxy.com/