Rails performance tips

So during the site’s down time, I started looking at performance. I’m not that concerned in squeezing out an extra tenth of a second out of page load time, but I do want the site to be able withstand a digging for as long as possible.

Eager loading

Rails lets you easily define associations between classes and use these associations to look up objects. The problem that arises is that without eager loading, each time you use an object, it results in another query to the server. In my case, a user has many styles, a style has many comments, and a comment was written by a different user. For a user’s page, I would iterate over his styles, his comments, and comments on his styles. So for loading a single user’s page, each of the following would get its own query:

  • The user
    • The user’s styles
    • The user’s comments
      • The style the user commented on
    • Comments on styles by that user
      • The user than made the comment
      • The style the comment was on

Using Rails’s eager loading feature, I greatly reduced the number of queries required. Using it is as simple as including an extra parameter in ActiveRecord.find - :include => [:styles, {:comments => :style}]. This tells it to eagerly load all the styles associated to that user, all comments associated to that user, and all styles associated to the comments. (It doesn’t load the comments on styles by the user because I haven’t figured out how to make it sort the way I want).

Excluding large, unnecessary columns

ActiveRecord’s find will by default load all columns, which is normally fine. In my case, I have a column that contains the CSS of the styles that can be up to 65KB. When the site generated a list of styles, it would get this column even though it wasn’t used on the page. This also caused out of memory exceptions for style listings. To prevent this, I figured I’d use the find method’s select parameter to only get the columns I cared about. Unfortunately, due to a bug in Rails, the include parameter (the one mentioned in the last section) overwrites the select parameter. I ended up putting the CSS in another table and creating a one-to-one relationship.

Creating metadata on create/update

The site categorizes styles  into app styles, global styles, and site styles. What category a style goes in is determined by various things in its CSS. When I first created the site, no meta data was stored about styles - to display the categories, the CSS of each style was parsed to determine its category. Long ago I had changed this - upon saving a style, various bits of information about the CSS (for example, the default namespace) were stored and these bits of information were used to determine the category. Now, the site determines the category on save, which results in much better performance when displaying categories.

Indexing the database

I guess I slept through this class at college. Put indexes on any column that you use in a WHERE condition.

Caching

I’ve just started working on this, but it seems very easy and intuitive in Rails. Page caching will generate static HTML pages out of the result of certain actions. In the controller, you say which actions get cached, then you create a “sweeper” that tells Rails when to re-generate the page. It’s simple, but there are a few caveats. The pages you cache will be rendered identically for each user, so there can’t be any differences for logged in users, etc. This stops me from caching individual styles’ pages, because there’s a difference in how comments are displayed. The pages will be rendered identically no matter what the parameters passed in. The pages will also be sent as text/html, no matter what your controller specifies. This is a problem for feeds, but clients don’t seem to mind this broken behaviour.

Storing HTTP resources outside of the database

I used to store screenshots in the database, which results in a lot of data being passed through the DB and Rails. I switched it around so that the screenshots are stored in the public folder, and I only store the path in the database.

3 Responses to “Rails performance tips”

  1. Rails学习笔记 » Archive » 有关Rails性能的链接总汇 Says:

    […] Rails Performance Tips Common Performance Problems in Rails - Pros and con between SQLSessionStore and MemChacheStore session containers, tips on optimizing queries, and general information regarding how to avoid slow helpers. The Adventures of Scaling: Stage 1, Stage 2, Stage 3, Stage 4 - A detailed explanation a Rails production architecture. Optimizing Rails Resource Usage - A short list of top Rails optimization tips, which include the proper use of caching. Sustainable Performance with Ruby on Rails - A 58 page PDF presentation describing railsbench, caching, session performance, and efficient Ruby code. Rails performance tips - A discussion on eager loading, excluding unnecessary columns, indexing database columns, and caching. Top 10 Ruby on Rails performance tips - Provides great tips to optimize your ruby code and how to handle finders. Performance related changes in Rails 1.1 - Discussion of performance enhancements in rails 1.1. Rails performance and caching, Part 2 - A discussing of Rails performance using caching. Stefan Kaes - Rails Performance - RubyConf 2006 conference notes on the Rails Performance presentation given by Stefan Kaes. Stefen Kaes - Optimizing Rails - Another post on the Rails 2006 presentation by Stefan Kaes on Rails Performance. Rails performance with FastCGI and Apache - A blog post on performance with Apache. Rails Performance Tool Box - A list of tools that come in handy when optimizing a Ruby on Rails application, which include query analyzer, query trace, and mtop. Rails Caching Documentation - Documentation for action, page, and fragment caching. The effect of using Rails fragment caching - Goes over the performance boost of using fragment caching. […]

  2. Development for Ruby on Rails Says:

    Excellent tips. I think that by loading early the styles associated with the comments you might also save a couple of seconds of downloading time once the styles are cached.

    Jenn

  3. dominiek Says:

    Cool,
    I also did a little writeup of my experiences on optimization:

    http://dominiek.com/articles/2007/12/11/optimizing-rails-quick-and-dirty

    Hope that can help some people

Leave a Reply

Adventures in development - Web standards and Firefox extensions