Ruby on Rails Performance Series :  Solving Ruby on Rails Performance 

Bottlenecks with RM­Manage www.FiveRuns.com 

develop great applications quickly, but how will they perform in the real-world? In this article we demonstrate how you can use FiveRuns RMManage to investigate Ruby on Rails application performance bottlenecks in your production
Ruby on Rails allows you to environment. Using the example below, we’ll show you how we used the metrics provided by FiveRuns RM-Manage to improve performance of your applications by over an order of magnitude! Of course, your results may vary, but armed with this information and FiveRuns RM-Manage, you can learn how to identify performance bottlenecks and resolve them.

For this article, FiveRuns'

Mike Perham created a simple blogging application based on Ruby on Rails

2.0 called Megaphone shown below:

For the rest of this article we focus on the Ruby on Rails Application Browser which is core to RMManage. It breaks down requests to your actions and extracts model and view processing times. These metrics are then aggregated over time from your production or development environments. Baseline To start analyzing Megaphone we added a large dataset to its database. Initially, we created 10,000 blog posts with 20,000 comments randomly mixed among the posts. Now, when we hit the front page it takes a very long time to display those entries. Let’s look at the RM-Manage Application Browser and see what is happening under the covers:

At the top of this screen shot you can see that the Average Response Time for this action was over 125,000 ms. Wow, over 125 seconds to respond to a request! To identify the most obvious bottlenecks let’s see how we can break down the numbers in the Application Browser screenshot above:

1. All shown data from the context of the index action of PostsController (which is not
displayed in the screenshot) so you can see which actions are taking most of the time. The vast majority of time is spent in the model layer (84%), indicating we need to do some ActiveRecord or database tuning. The models User and Category are each called over 9000 times. Looking at the view code, we can see that the code only uses <%= post.<model>.name %>. This indicates that it is an N+1 query problem where we should be using an ActiveRecord :include  clause to pull in the data with a join. Note that each call averages less than a millisecond. This signifies a cache hit since a call to the database will almost always take 5-10 msec, minimum. The model Comment is called almost 10,000 times also but it is hitting the database (since the time is about 10 msec average). Looking at the code, we call <%= 
post.comments.size %>

2.

3.

4.

5.

Post is only being called a few times but those calls are slow. This indicates we have a

slow query which could benefit from an index or some other tuning. When conducting Ruby

on Rails performance tuning, it’s vital to have a baseline number to

compare your results. Since the actual page render took 125 seconds, we’ll use this as our performance baseline. Tuning We’ll tackle the biggest problem first since that will get us the greatest speedup. (4) is the biggest problem because 9487 * 10 msec = 94.87 seconds. To solve (4), we have two potential solutions:

 

Add an index to the comment.post_id column so that the count query is much quicker. Add a counter_cache column which holds the number of comments associated with a post.

An index will get us a nice speedup but ultimately still requires 10,000 separate queries. Adding an index will reduce the time dramatically but adding a counter_cache gets us an even greater speedup. This is straightforward. We just create a migration for adding a column to the Posts table:

    t.integer :comments_count, :default => 0

and update the belongs_to line in our Comment model:

    belongs_to :post, :counter_cache => true

For a tip on how to initialize the counter_cache column, please see Mike’s

blog entry on this topic.

Check out the speed up! Now we’re at 32 seconds, down from over 125 seconds:

Please note that by adding the counter_cache we have removed all invocation of the Comment model from this action. This is because the pre-calculated counter resides inside the Post model. To solve (3), we modify our code to use the :include option. Before the action contained the following:

  @posts = Post.find(:all, :order => 'posts.created_at DESC')

After the change it looks like this:

  @posts = Post.find(:all, :order => 'posts.created_at DESC', :include => [:author,  :category])

Now we’re at 13 seconds with the majority of the time in the view layer rendering the set of 10,000 posts. That’s about an order of magnitude performance improvement. But, we can do better. Production Until this point, we have only shown aggregated metrics associated with single requests to an action. That’s useful in development and staging environments. However, the Application Browser becomes even more useful in production environments. Due to its low overhead you can take an in-depth look at your application under real (or artificial) production load.

From a user’s perspective including 10,000 posts on the front page isn’t too useful so we’ll want to trim this down to something reasonable like the latest 20 posts. Here we have launched a number of httperf processes against Megaphone which is now configured to show only the 20 most recent posts on its homepage:

As you can see, doing this gets us to the final speedup we need with roughly 66ms per request. Please also note that unlike in the other scenarios the action was invoked 28,156 times. The shown metrics have been aggregated over all these requests. We have learned from our customers they need to understand what is going on in their production environments. With larger applications it can become difficult to simulate user traffic that mimics the real world load. A certain bottleneck might never expose itself in a staging environment with artificial load. That’s why we focus on optimizing our Rails instrumentation layer to have minimal impact on the target system so it can be used in production environments. Efficient aggregation is key to that challenge. Ruby on Rails Performance Notes We used a simple example above to show how you can use RM-Manage to improve Ruby on Rails performance. Of course, the Ruby on Rails framework offers many capabilities, like built-in caching, which would solve some of the above performance problems. The benchmark numbers above reflect response times of a single instance Ruby on Rails application served by Mongrel on a desktop system. In the real world such applications typically run with multiple Mongrel processes behind a load balancer. RM-Manage supports such configurations out-of-the-box. It will aggregate Ruby on Rails performance metrics across all Mongrel processes running on a particular server. The general steps outlined above still remain in such an environment. In this scenario we used Ruby 1.8.6 but we also could have used JRuby . We have outlined our RMManage support for JRuby in a previous Ruby on Rails application blog post . Results In the above example, we’ve taken our blogging system’s index page render from 125 seconds to 0.065 seconds, that’s a 1900x speedup! To do this, we used the RM-Manage Application Browser to find hotspots in our code and employed several strategies to fix the code:

  

database tuning with cache columns, query tuning with ActiveRecord options, and functional tweaks to the application.

Of course, Megaphone is only a simple application that we use to highlight typical Ruby on Rails performance problems. But, the same principles can be applied to any Rails application. Imagine the positive impact of improving the performance of your business-critical Rails application. More information about developing Ruby on Rails applications and monitoring Ruby on Rails performance is available at www.fiveruns.com. If you think FiveRuns RM-Manage could help you, it’s easy to get started and test drive it for yourself. Just sign up for a free 30 day trial.

Resources: Ruby on Rails Performance Monitoring –

www.fiveruns.com

Ruby on Rails Blog