Optimizing your static website is worthwhile too!


My wife’s wheat allergy website, www.wheat-free.org, has been operating for 15 years. In that time it has had a few major overhauls. It started out as a Dreamweaver site and then in 2004, was rebuilt as a static generated site with content stored in XML files and a custom site generator built in Java. That version remained until 2013 when it became time to make the site mobile optimized. Although a new responsive front-end could have been added to the existing site generation backend, I decided to build a new static site generator with technologies I use today: Python , Flask and, YAML files for storing content and configuration.

All incarnations of the site have been static therefore the basic performance of page serving has never been an issue. However, with an increasing proportion of site visitors coming from mobile devices, it’s still important to look at all ways to improve performance of the site. For the responsive site I built in 2013, there were a lot of potential optimizations that, because it was our own site, I just put off to another time.

Better performance never hurts, so I finally got around to the optimizing the site 18 months after its relaunch as a mobile friendly site.

This blog post documents the impact of individual performance improvements made to the site. The performance numbers should only be taken as a guide since it's difficult to correlate numbers between the test environment here and the real world.

Testing environment

The test to compare the old hosting platform with the new hosting was run against the respective hosting providers services. All other comparative tests between different configuration changes were measured on a locally running Virtualbox VM, configured with Ubuntu 14.04, 4GBytes of SSD RAM and 2 CPUs. JMeter was used for load testing and was run from the VM host.

Performance impact of old server technology

The site had been on the same shared hosting account since 2004. The servers were, no doubt, upgraded in that time but the filesystem was on network storage and hard drives. I suspected moving the site to a lightly utilized Linode 2GB SSD server that I already had would improve performance. Also, the Linode server is located in the Newark, NJ data centre as opposed to Oregon for the old server. Newark is more central to the majority of site visitors so it will reduce their latency, but conversely increase latency for visitors from Asia and Oceania.

Results

  Old hosting Linode 2GB SSD Improvement
Average request time 141ms 73ms 50%
Transfer rate 168 KB/sec 319KB/Sec 90%

Old hosting specification:

  • AMD Athlon(tm)64 X2 Dual Core Processor 3800+
  • 2GHg
  • Networked hard drive storage
  • Shared hosting

Linode hosting:

  • Intel(R) Xeon(R) CPU E5-2680 v2
  • 2.80GHz
  • Local SSD storage
  • 2048 GByte Virtual Server

Conclusion

Don’t let your hosting hardware get too out-of-date - even for a static site. Running on current hardware with local SSD storage, has a significant impact on site performance and is highly recommended.

Performance impact of Apache .htaccess file

As mentioned, the site has been running for 15 years, during that time, as we’ve improved our SEO practices, there’s been a lot of URL changes. That’s resulted in a .htaccess file with over five hundred redirects in it. Irrespective of the number of redirects we have, the Apache documentation recommends against the use of an htaccess file because it’s read and parsed on each request.

This testing compares the performance of the site with and without a .htaccess file.

Results

The following table shows the impact on average request time of disabling htaccess for loading the site homepage HTML only.

  Average request time Improvement
.htaccess enabled - 500 redirects 44ms Baseline
.htaccess enabled - 0 redirects 40ms -4ms
.htaccess disabled 39ms -5ms

JMeter setup: threads: 3; loop count: 1000

The following table shows the impact on average request time of disabling htaccess for loading the site homepage HTML and embedded assets.

  Average request time Improvement
.htaccess enabled - 500 redirects 1044ms Baseline
.htaccess enabled - 0 redirects 1005ms -39ms
.htaccess disabled 988ms -56ms

JMeter setup: threads: 3; loop count: 1000; Retrieve All Embedded Resources; URLs must match: http://www\.localhost:8000/.*

Conclusion

The improvement is small per request but this is multiplied up by the number of requests to internal page assets on the same server. From the results it’s clear the biggest impact on request time was the large number of redirects in the htaccess file. Having an htaccess file with minimal directives has a far less significant impact.

I didn’t want to loose the five hundred redirects or put those redirects in the main Apache configuration file. The solution I went for was a custom PHP 404 page that handled redirects by looking up in an associative array to determine if a request is a true 404 or a URL that needs redirecting.

Performance impact of Apache access logs

It’s very rare that I look at Apache access logs on live servers and on the few occasions I have in the last year or two, it’s been to identify sources of brute force attempts on the login of dynamic sites. Also, I don’t use any access log analysis programs instead relying on Google Analytics for traffic analysis. Why then have them enabled by default?

This test compares performance of the site with and without access logs turned on.

Results

The following table shows the impact on average request time of disabling access logs for loading the site homepage HTML only.

  Average request time Improvement
Access log enabled 39ms Baseline
Access log disabled 39ms 0ms

JMeter setup: threads: 3; loop count: 1000

The following table shows the impact on average request time of disabling access logs for loading the site homepage HTML and embedded assets.

  Average request time Improvement
Access log enabled 1005ms Baseline
Access log disabled 998ms -7ms

JMeter setup: threads: 3; loop count: 1000; Retrieve All Embedded Resources

Conclusion

No improvement could be measure loading the HTML of the page only. When embedded assets were included a small improvement can be seen but with browser caching even that will only be seen on full page loads.

Minimize assets loaded

The site had a number of asset requests that could easily be eliminated. This included a bug where one asset was being downloaded twice due to some references to it having a caching busting query string appended and others not.

This test compares response times and throughput as assets were combined, eliminated or inlined.

Results

The following table shows the impact on average request time and throughput of progressively reducing the number external assets for loading the site homepage.

  Average request time Throughput Improvement
Original server assets 861ms 22.9/sec Baseline
Two CSS file requests removed 782ms 25.3/sec -79ms
Four JS files combined and compressed 741ms 26.9/sec -120ms
Page specific JS snippet inlined and additional JS file combined. 703ms 28.2/sec -158ms
8 background & list style images combined into a single sprite 698ms 28.4/sec -163ms

JMeter setup: threads: 20; loop count: 1000; Retrieve All Embedded Resources; URLs must match: http://www.localhost:8000/.*

Conclusion

Overall this gained 163ms on request time in the test environment. Given the test environment would have close to zero network latency, you’d expect more to be gained on the live site.

Minify HTML

HTML minification was added to the static site generator using django-htmlmin (it works for Flask as well as Django).

This test takes the largest page on the site (the glossary page) and compares its average request time before and after minification

Results

  Average request time Throughput Improvement
Before minification 38ms 494/sec Baseline
After minification 39ms 479/sec +1ms

JMeter setup: threads: 20; loop count: 1000

Conclusion

The results were a little surprising in that, given the test environment, I was expecting little or no improvement on a relatively small HTML file. What I actually experienced was a consistent but small decrease in performance with the minified version. The minification did reduce the raw HTML file from 69,787 Bytes to 55,859 Bytes, however this had no positive impact on request time under test.

The test environment differs from a live environment in the follow ways:

  • JMeter was not configured to request gzip content.
  • Unlimited bandwidth
  • No network latency

None of the above explain the decrease in performance when the HTML was minified though.