How to improve website performance

Posted by Julian Seidenberg on 17 October 2012

Julian is one of our SilverStripe developers who works with the rest of the team on large projects, as well as acting as technical lead on projects of his own. He is an American citizen who has never lived in America. He was born and grew up in Germany, studied in the UK, and is now happily living in New Zealand, whose friendly, laid-back culture and natural beauty he values.

We all want our websites to be fast. Customers prefer faster websites, they buy more, stay longer, and experience more enjoyment, all because of a faster site. Moreover, research indicates that people are getting increasingly less patient with slow websites:

Additionally, faster page downloads from a website means reduced server load per page view. Reduced load means the server is able to handle more concurrent users. More concurrent users means you can run your site on cheaper hardware and use a simpler deployment. Perhaps a single server will do where you would otherwise have to buy, deploy and manage four servers running in parallel. Improving performance is a win, win, win.

So, as web developers, we are naturally thinking of ways in which we can speed up our websites. Here is a guide for things you can do with SilverStripe to improve performance.

Measuring

The first step should always be to measure how fast your site already is. That will allow you to see how much your changes improve things. Here are some tools that you can use to measure your website’s speed:

Okay, now that we’ve measured our current speed. Here some practical tips for making things faster.

Static caching

Static caching makes a huge difference in page speed. Instead of SilverStripe generating each page dynamically on every request, it can cleverly write a “static” file with the contents of a page to the filesystem. A web server like Apache, Nginx, or LightTPD can serve up static files much faster than PHP can build a dynamic page (like one or two orders of magnitude faster). The difficult thing is knowing when to update the static pages and/or how to manage the parts of the page that need to be dynamic.

We use static caching to great effect on the websites we build that need to hold up to a huge amount of traffic. Read this guide on how to set up SilverStripe with static caching.

Partial caching

Partial caching is not nearly as intimidating to set up as static caching. However, it provides some very nice performance gains for relatively little effort. The idea behind partial caching is that parts of your website, for example, a menu that goes 5-levels deep rarely change and therefore do not need to be re-calculated on every single page request. With partial caching we set a cache-key (e.g. the maximum last edited value of the SiteTree database table) that is quick to query. As long as the cache-key remains the same, i.e. as long as no one updates the SiteTree structure, we fetch that part of the page HTML from the cache storage instead of generating it with PHP code.

If done correctly, partial caching can give you a 2x - 5x performance boost with no downsides. Choosing the correct cache-key is critical though. With the right cache-key, users will never even know that caching is there working its magic.

Every website should implement some partial caching. Read more about it in this comprehensive guide.

Combining and minifying Javascript

We’ve already covered this in our Javascript performance tips. See tip number nine here.

Combining and minifying CSS

If you use a CSS-extension like SCSS you can use that to combine and minify your CSS. SCSS is great for a whole bunch of other reasons too. It takes much of the pain out of writing CSS. Try it!

To combine your CSS, first write it in nicely separated SCSS files (SCSS is fully backwards compatible to CSS, so you can just copy and paste if you are in a hurry), then import all these files into a single “style.scss” file that looks a bit like this:

@import "_clear.scss";
@import "_typography.scss";
@import "_layout.scss";
@import "_shared.scss";

Compile your SCSS into CSS, then using the SilverStripe Requirements system, include the single CSS file that was generated. You might put something like the following in your “Page_Controller” class:

public function init() {
parent::init();
Requirements::themedCSS('style');
}

In addition to combining CSS, you can use a tool like Compass to help you minify the CSS code. If you want maximum performance, then set the following in your “config.rb” file (when you create a new Compass project the Compass compiler will create this configuration file in your project directory):

output_style = :compressed

That configuration setting tells Compass to minify the output CSS, removing all unnecessary comments, white-space and newlines. It can be a bit disconcerting to see all your site’s CSS on a single line, but it nicely reduces the amount of data the browser needs to download and parse.

The end result of all this is that you get a single minified CSS file for your entire website where you would otherwise have a bunch of different files, all of which the browser would have to download separately, slowing down your website.

Htaccess tweaks

The htaccess file controls what Apache does when serving content. However, tweaking these settings is a bit of a black art. Similar to Quantum Mechanics, I’m pretty sure noone knows exactly how htaccess directives work (haha).

SilverStripe deliberately keeps its basic htaccess file quite simple. Hopefully some people can therefore fully understand what is happening in there and modify the htaccess file to suit their needs. Here are some tips on what you can add to your htaccess file to gain immediate and significant performance benefits for your website.

Expiry headers

We can set expiry headers in the .htaccess file. This will tell the user's browsers that it should store/cache certain static files for a length of time and not re-request them on every page view. So the browser downloads all the static resources when the user first visits the website, but then never again for the next seven days, using the cached copies instead.

It does this with one exception: SilverStripe automatically adds a clever “cachebuster” suffix (e.g. “.../screen.css?m=1312462080”) to the end of Javascript and CSS files included through the Requirements system. So, when you release a new Javascript file, the changes will take effect immediately because the new suffix tricks the browser into seeing a new file, bypassing the cached version (note: SilverStripe creates the suffix using the “filemtime” function based on the modification date of the file).

<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/html "access plus 5 minutes"
ExpiresByType image/gif "access plus 7 day"
ExpiresByType image/png "access plus 7 day"
ExpiresByType image/jpg "access plus 7 day"
ExpiresByType image/jpeg "access plus 7 day"
ExpiresByType image/ico "access plus 7 day"
ExpiresByType text/css "access plus 7 day"
ExpiresByType text/javascript "access plus 7 day"
ExpiresByType application/x-javascript "access plus 7 day"
</IfModule>

Notice how this example includes a 5-minute cache of the HTML itself. Any change to the site will take five minutes (or a hard refresh) to appear, but you get some nice additional performance in times of high load. If your content doesn’t have to be up-to-the-second fresh, then adding an HTML expiry header might be a good idea.

Disabling e-tags

If you are sure you got your expiry settings right, then you can override the browser’s checking for expired content by disabling “if-modified-since” and “e-tags”. This means that the browser doesn’t send a quick “is this really expired” message to the server, but instead fully relies on the values set in the original file header. The result: even better performance, especially so in a multi-server environment.

<IfModule mod_headers.c>
Header unset ETag
RequestHeader unset If-Modified-Since
RequestHeader unset If-None-Match
FileETag None
</IfModule>

Compressing content

Just about every web-browser (yes, even IE6) supports compressing of the text-content on a webpage. Compressing text content can drastically reduce the amount of data the server needs to transfer to the client. You can save as much as 80% of the bandwidth by using compression.

However, beware, attempting to compress files that are already compressed can sometimes cause problems, even potentially corrupting the files in question. So be careful to exclude such files from the compression.

Here is how to add “deflate” compression.

<IfModule mod_deflate.c>
SetOutputFilter DEFLATE
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
# Don't compress images, movies or zip files
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
SetEnvIfNoCase Request_URI \.(?:exe|t?gz|zip|bz2|sit|rar)$ no-gzip dont-vary
SetEnvIfNoCase Request_URI \.(?:avi|mov|mp3|mp4|rm|flv|swf|mp?g)$ no-gzip dont-vary
<IfModule mod_headers.c>
# properly handle requests coming from behind proxies
Header append Vary User-Agent
</IfModule>
</IfModule>

Note: if “deflate” compression gives you trouble, then you can try “gzip” compression in Apache as an alternative.

Do you know of any more we can do to improve site performance? Please share your own tips and insights in the comments.

Post your comment

Note: Comments are moderated and won't show until they are approved

Comments

  • Hi Julian,

    Thank you for this very usefull post - it happened to be posted just as i needed to optimise a site.

    Just with some htaccess tweaks, combining of files and the partial caching i achieved very nice results.

    Btw. i used tools.pingdom.com and Google Page Speed Insights for FF to guide me.

    Posted by Thomas B. Nielsen, 2 years ago

  • Check into XHProf for profiling sites too - brilliant for identifying bottlenecks.

    Posted by Marcus, 2 years ago

  • Thanks for all the tips, everyone. Thanks especially for recommending Varnish. I haven't used that myself yet, but will now look into it.

    Peter, the initial load could be taking longer because of SilverStripe is rebuilding the template cache. I think your /tmp directory might be getting cleared automatically every day, so on the initial page load, SilverStripe needs to recompile all the templates. A process that can take ~4 seconds. This is the same thing that happens when you add ?flush=all to your URL.

    Simon, thanks for the htaccess tip. One thing to be aware of when turning off "AllowOverride" is that the htaccess file in the SilverStripe assets folder sets a bunch of upload filetype protections to prevent hacking of your server. Be sure to include these in your virtual host configuration so that you don't open yourself up to security risks.

    Thanks again,
    Julian

    Posted by Julian Seidenberg, 2 years ago @candidasa4

  • So I have some silverstripe websites on a shared host (I know, thats stupid...), and what I noticed is that they are quite fast / fast enough but only *after* the initial (load of the day). The initial load takes sometimes about 4-5 seconds, so really noticeable.

    Have you any idea what causes this and how to circumvent it?

    Posted by peter, 2 years ago

  • Awesome, Julian, nice writeup.
    As Simon just pointed out, I agree, installing an optcode cache can really speed things up as well (as it caches the PHP precompiled code).
    I've tried xcache and APC, and stayed with APC as it seems to be more maintained.

    Furthermore for REAL speed improvements when static caching is not an option, try a tool like Varnish. I've tested it on one SS site where the host set it up, and installed it myself (as proof of concept) on an own box.
    It checks each page's headers, and only if a new page is needed, it fetches them from Apache, else it serves a memory-cached version. For an idea of what kind of speed this can give you on a normal SS install, check out http://www.intoeternitythemovie.com/

    Posted by Anselm Christophersen, 2 years ago @anselmdk

  • I was quite surprised to see no mention of Varnish being used as a reverse proxy cache. Also tools like logstalgia or gltail to visualize back end log activity are useful. If caching is not correctly configured then it becomes quickly very obvious.

    Posted by Gordon Anderson, 2 years ago @elgrodo

  • Some things that can really speed things up:
    - Install a php-opcode cache. Our server load dropped from 1-2 under normal use to 0.05 after installing xcache.

    - Move your .htaccess content into your virtual host and disable .htaccess files with AllowOverride None. This stops Apache looking for a .htaccess file in your site folder, and the folder above that and above that and so on. A .htaccess also has to be parsed each request, whereas a virtual host only needs to be parsed whenever the server is restarted.

    - Try not to use concurrent AJAX requests. PHP locks the session file, so will only process one request/session at a time. That means AJAX requests need to wait for the ones before it to finish before being handled.

    Posted by Simon, 2 years ago @simonw

RSS feed for comments on this page | RSS feed for all comments

Want to know more about the company that brought you SilverStripe? Then check out SilverStripe.com

Comments on this website? Please give feedback.