In this conclusion to a six-part article series on optimizing the performance of a Ruby on Rails program, you'll learn about the most common performance problems you might encounter in Rails, and how to avoid them. This article is excerpted from chapter 13 of the book Practical Rails Projects, written by Eldon Alameda (Apress; ISBN: 1590597818).
Common Performance Problems in Rails - Database Access (Page 2 of 2 )
We already mentioned in Chapter 4 how fetching a list of books and then referencing their authors can lead to 2n+1 queries, but itís worth a reminder. Use the :includeparameter in finders if you know you are going to use the associated objects, like authors, on the page:
def index @book_pages, @books = paginate :books, :per_page => 10, :include => [:authors, :publisher], :order => "books.id desc" end
Another common database performance problem arises when you forget to add needed indices to the database. This is not a Rails-specific problem per se, but it is easy to forget when building an application with small sample data. When the amount of data then grows, you might start wondering why queries that were so snappy when testing are now taking a minute or an hour (no kidding!).
If you track the root of a slow action to a certain database query (or find one by following the slow query log with MySQL, as described in Chapter 10), the next step is to analyze that query. Most database vendors have their own tools for that. In MySQL and PostgreSQL, you can useEXPLAIN [SELECT query]for this. As an example, letís runEXPLAIN on a query that is run whenever the index page of the catalog controller is loaded (weíve truncated the output lines to make them more readable):
mysql> EXPLAIN SELECT COUNT(DISTINCT books.id) -> FROM books LEFT OUTER JOIN authors_books -> ON authors_books.book_id = books.id -> LEFT OUTER JOIN authors -> ON authors.id = authors_books.author_id -> LEFT OUTER JOIN publishers -> ON publishers.id = books.publisher_id;
id select_type table
4 rows in set (0.00 sec)
Weíre mostly interested in thetypecolumn of the output. For the two first tables,books andauthors_books,ALLmeans that a full-table scan is done, which should always be at least a bit alarming. If we wanted this query to stay fast when more data is imported in the database, we should make the query use an index for theauthors_bookstable. We can do that easily with a migration:
In thekeyfield, you can see that the index used isauthors_books_book_id_index, the index we just created with the migration.
Tip For more information about usingEXPLAINin MySQL, seehttp://dev.mysql.com/doc/refman/ 5.0/en/explain.html. For details on the PostgreSQL version, seewww.postgresql.org/docs/8.1/ interactive/sql-explain.html.
As a rule of thumb, you should create indices for all foreign key fields, as well as for all columns you are using inWHEREclauses. A good example of such a column istags.name, which is used extensively by theacts_as_taggablecode to find tags in the database table.
In this chapter, we took a look at how to measure and optimize the performance of a Rails application. We introduced the Rails Analyzer set of profiling tools. Then we added different caching mechanisms to our application, for entire pages and content chunks that are either needed very often or that take expensive operations to be produced. Finally, we took a look at common performance problems in Rails applications and how to solve them.
Measuring and optimizing a web application are constant processes. It is not enough to make the application fast once and then lean back and enjoy the profits. User behavior changes over time, and the hit rates go up (hopefully), so it pays to follow closely how the site behaves.
Optimizing a Rails application is a topic that merits a book or three just for itself. We could only scratch the surface in this chapter. For a lot more information on this topic, follow Stefan Kaesís blog athttp://railsexpress.de/blog/, and look for his upcoming book on the topic, Performance Rails: Building Rails Applications with Sustainable Performance (ISBN: 0-32147-741-3).
DISCLAIMER: The content provided in this article is not warranted or guaranteed by Developer Shed, Inc. The content provided is intended for entertainment and/or educational purposes in order to introduce to the reader key ideas, concepts, and/or product reviews. As such it is incumbent upon the reader to employ real-world tactics for security and implementation of best practices. We are not liable for any negative consequences that may result from implementing any information covered in our articles or tutorials. If this is a hardware review, it is not recommended to open and/or modify your hardware.