Rails performance tip - using YSlow
YSlow from Yahoo! is a Firefox add-on to analyse web pages and tell you why they’re slow based on rules for high performance web sites. YSlow requires the indispensable Firebug extension.
The 13 rules YSlow checks your site against are as follows:
1. Make Fewer HTTP Requests
2. Use a Content Delivery Network
3. Add an Expires Header
4. Gzip Components
5. Put CSS at the Top
6. Move Scripts to the Bottom
7. Avoid CSS Expressions
8. Make JavaScript and CSS External
9. Reduce DNS Lookups
10. Minify JavaScript
11. Avoid Redirects
12. Remove Duplicate Scripts
13. Configure ETags
This post will demonstrate that most of these are easily achievable for a Rails website through a combination of plugins and with correct configuration of a proxy web server (in front of a mongrel cluster) – in this case Nginx. This guide follows experience with improving performance for trawlr.com (an online RSS reader).
Make Fewer HTTP Requests, Minify JavaScript, Put CSS at the Top, Move Scripts to the Bottom, Remove Duplicate Scripts
The easiest way to make fewer HTTP requests is to combine all JavaScript and CSS files into one. The asset packager plugin does exactly this, plus it will also compress the source files (in production mode) and correctly handles caching (without query string parameters).
Moving CSS to the top (within the head section) and moving JavaScript to the bottom of the page are both manual tasks that should be done in the layout templates (such as app/views/layouts/application.rhtml). Remember to use stylesheet_link_merged :base and javascript_include_merged :base rather than the default Rails helpers.
By using asset packager you can also verify that scripts are only included once – another performance hit otherwise!
Excluding the Google analytics JavaScript file, trawlr.com now uses a single css and js file (including the entire prototype library). Note: You may need to add a missing semi-colon as per this defect for prototype to work correctly.
Asset Packager can be included as part of a Capistrano deployment with the following recipe:
desc "Compress JavaScript and CSS files using asset_packager"
task :after_update_code, :roles => [:web] do
run <<-EOF
cd #{release_path} &&
rake RAILS_ENV=production asset:packager:build_all
EOF
end
Use a Content Delivery Network
Ignoring this point for now; I’d suggest the use of Amazon S3 as a useful starting point for simple CDN.
Add an Expires Header
A first-time visitor to your page may have to make several HTTP requests, but by using the Expires header you make those components cacheable. This avoids unnecessary HTTP requests on subsequent page views. Expires headers are most often used with images, but they should be used on all components including scripts, stylesheets, and Flash components.
Nginx allows adding arbitrary HTTP headers via the expire and add_header directives. Adding the expires header to static content is done with a regular expression looking for relevant file extensions in the request URL. This example uses the maximum expiry date but could be set to more appropriate values as required (e.g. 24h, 7d, 1M)
# Add expires header for static content
location ~* \.(js|css|jpg|jpeg|gif|png)$ {
if (-f $request_filename) {
expires max;
break;
}
}
Gzip Components
Nginx can gzip any responses – including those proxied from a mongrel cluster.
gzip on; gzip_min_length 1100; gzip_buffers 4 8k; gzip_proxied any; gzip_types text/plain text/html text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;
Avoid CSS Expressions
Just don’t do it!
Make JavaScript and CSS External
Add you JavaScript and CSS styles in external files rather than inline. The added benefit here is that the content will be merged and compressed thanks to the work already done above.
Reduce DNS Lookups, Avoid Redirects, Configure ETags
These weren’t an issue for me so I suggest the Yahoo! guidance for further information
Summary
After making the changes outlined above the YSlow score for trawlr.com has hit a B grade (89) with all points A grade except “Use a CDN” which I have not addressed. The “Stats” view indicates that with an empty browser cache there would be 30 HTTP requests (26.0K total size), with a full cache this drops to a single request (the HTML document) (6.5K total size). Worth the effort in my opinion!


Thanks, good guide.
On the CDN front I am pretty sure S3 isn’t a CDN as it is at most in two locations. Hopefully S3 starts offering a CDN option though.
Technically, you can ‘cheat’ with the CDN rule – if you spread your accesses out over several domains (with something like asset_hosts), there’s an about:config setting in which you can list your own domains for the CDN.
I did that for now since I think only akamai and yahoo are currently listed in their CDN list.
URL: http://www.slashdotdash.net/articles/2007/07/31/rails-performance-tip-using-yslow
Performance Grade: D (63)
B 1. Make fewer HTTP requests
This page has 5 external JavaScript files.
This page has 3 external StyleSheets.
F 2. Use a CDN These components are not on a CDN:
You can add your own CDN hostname preferences.
F 3. Add an Expires header These components do not have a far future Expires header:
C 4. Gzip components These components are not gzipped:
C 5. Put CSS at the top 2 external stylesheets were found outside the document HEAD.
C 6. Move scripts to the bottom 4 external scripts were found in the document HEAD. Could they be moved lower in the page?
A 7. Avoid CSS expressions n/a 8. Make JS and CSS external Only consider this if your property is a common user home page. A 9. Reduce DNS lookups C 10. Minify JS The following JavaScript files do not appear to be obfuscated nor minified.
A 11. Avoid redirects A 12. Remove duplicate scripts A 13. Configure ETags
@The Smartass Who Ran YSlow on This Site
Ha, I never even tried it on this site… I’m in the process of upgrading to the latest version of Typo (along with a new design) so I’ll see if I can up that number :-)
Thanks for your YSlow presentation. It’s a very interesting tool to improve websites speed.
Regarding the number of HTTP connections due to lots of small images, I recommend reading : http://www.alistapart.com/articles/sprites/
I’d have to agree with all of this except the comment about javascript. As a front end developer, it’s generally bad practice to have javascript anywhere but in the HEAD of a document.
One bit about ETags which I initially found quite confusing – YSlow is actually going to penalize you for using them, not for omitting them. The point they’re trying to make is that unless you’re using a cluster of servers, making use of ETags to detect file changes is unnecessary overhead.
If you do use multiple servers though, the Apache directive to do this correctly is:
FileETag MTime Size
This prevents apache from using inode information in it’s etag calculation (which, for us rails users, is going to change on every run of capistrano).
For more details, I found this blog post very helpful: http://phaedo.cx/archives/2007/07/25/tools-for-optimizing-your-website-etag-and-expire-headers-in-django-apache-and-lighttpd/
The expires header command doesn’t seem to be working (at least for me). And if I add the simpler expire: if (-f $request_filename) { expires 10y; break; }
YSlow still complains that my static assets have no expires headers. Any thoughts?