Dr. Dobb's Journal - December 2008 - (Page 24) d12weis_p4db 10/10/08 9:30 AM Page 24 Core Technology PERFORMANCE ON RAILS continued from page 20 dozen controllers, and about 100 view templates. That translates into at least a few thousand lines of code. Now where are your bottlenecks? Consider your application as a whole. What are the most popular page requests? Are there any obviously slow responses? If your app is small enough, you might be able to answer this off the top of your head with confidence. Otherwise, I suggest gathering some statistics about your application’s usage and aggregate performance using a service like FiveRuns (www.fiveruns.com) or NewRelic RPM (www.newrelic.com), or build your own tools to mine some basic stats like frequency (how often a request is made) and duration (how long those requests took) from your logs. Once you’ve identified your problem areas, prioritize them, starting with the most popular slow pages. ignore any outliers and find an average response time. In the aforementioned example, the rendering time is on the high side, but almost half of the time was spent executing database queries, so I want to focus on that. Database Profiling and Tuning Most Rails applications use Active Record, a simple object-relational mapping (ORM) strategy that maps tables to classes, rows to objects, and columns to object attributes. Like any other ORM implementation, Active Record makes a lot of data-related tasks easy, but it’s also easy to run into performance problems if you treat the database as little more than a junk drawer. Many common database-related bottlenecks are the result of slow queries or the application executing too many queries. As fast and powerful as modern relational database engines are, your database won’t perform well without monitoring and an occasional tune-up. Many databases provide a “slow query log,” which logs every query that takes more than a specified amount of time— two seconds is a good place to start to find the worst offenders, but you should soon be looking for queries that take as little as 250 milliseconds (0.25s). Another option is to use a profiler or simply tail the log, examining the execution time of each query. Why is the query slow? There’s one way to find out for sure—use your database’s “explain plan” feature to see how the query is being processed. Chances are you’ll find a full-table scan (sometimes called a “sequential scan”) that you could fix by adding an index on some (or all) of the columns that you’re filtering or ordering by. You might find that an index is being used, but a better index could be added. There are too many variables for me to tell you what index would be best, so I encourage you to experiment and research the indexing strategies available in your choice of database. Add an index that you think will help and run the explain plan again. Try modifying the query to obtain the results in a slightly different, more efficient way. When you’ve tuned the query and found the right index, apply it to your project by modifying the code that builds the query and create a migration to add the index. Now consider a blog post, which can have many comments, each comment by a different user. Scanning the log file, you might find a query like this: User Load (0.000366) SELECT * FROM "users" WHERE ("users"."id"=7) This log entry shows that the User model was used to load the user with ID 7 from the database, and the query took 0.000366 seconds—pretty quick. However, a red flag should go up if you see a series of similar queries like this: Comment Load (0.004620) SELECT * FROM "comments" WHERE (“posts"."id" = 43) User Load (0.000366) SELECT * FROM "users" WHERE ("users"."id"=7) User Load (0.000306) SELECT * FROM "users” WHERE ("users"."id"=3) User Load (0.000426) SELECT * FROM "users" WHERE ("users"."id"=9) User Load (0.000378) SELECT * FROM "users" WHERE ("users"."id"=5) User Load (0.000452) SELECT * FROM "users" WHERE ("users"."id"=8) Benchmark Before making any changes to improve performance, you should have a point of reference, a benchmark, to know how much improvement you’ve actually made with each change, or if you’ve caused a regression. Load your slow page and see how long it takes to complete. The easiest way to do this is to watch your development log: $ tail -f log/development.log When you load the page, the last statement that is logged is something like this: Completed in 3.05327 (0 reqs/sec) | Rendering: 0.87915 (28%) | DB: 1.49075 (48%) | 200 OK [http://localhost/foo] This pattern indicates an n+1 problem. The list of comments for a blog post is fetched in one query, but fetching the user who wrote each comment (presumably in a loop) is n more queries (where n is the number of comments), so a post commented on by 50 readers will require no fewer than 51 queries to fetch all of the data. Even if each of those queries is relatively fast, they can quickly add up. The following code demonstrates the problem: # posts_controller.rb def show @post = Post.find params[:id] end # view - show.html.erb says This request completed in about three seconds, which isn’t very good. Your goal is to make that number as small as possible, increasing the number of requests per second. That translates into a more responsive application, but also an application that will be easier to scale because you’ll be able to handle more concurrent requests with fewer resources. It’s a good idea to run the same request a few times to make sure the metrics are consistent. There may be some outliers due to garbage collection or other system activity— 24 Solution 1: Eager Fetching with :include Rails provides an easy way to solve basic n+1 issues: eager fetching with the :include option. When fetching the post, we can fetch all of its comments and each corresponding user in far fewer SQL queries: Dr. Dobb’s Journal l www.ddj.com l December 2008 http://www.fiveruns.com http://www.newrelic.com http://www.ddj.com
Table of Contents Feed for the Digital Edition of Dr. Dobb's Journal - December 2008 Dr. Dobb's Journal - December 2008 Contents Friday Night Fish Fry Alia Vox Developer Diaries Conversations The Man Who Sold the Sky Performance on Rails LINQ-to-SQL and T-SQL A Remote Java RMI Registry Beyond B-Trees File Descriptors and Multithreaded Programs Effective Concurrency The Agile Edge Swaine's Flames Dr. Dobb's Journal - December 2008 Dr. Dobb's Journal - December 2008 - Dr. Dobb's Journal - December 2008 (Page Cover1) Dr. Dobb's Journal - December 2008 - Dr. Dobb's Journal - December 2008 (Page Cover2) Dr. Dobb's Journal - December 2008 - Dr. Dobb's Journal - December 2008 (Page 1) Dr. Dobb's Journal - December 2008 - Dr. Dobb's Journal - December 2008 (Page 2) Dr. Dobb's Journal - December 2008 - Dr. Dobb's Journal - December 2008 (Page 3) Dr. Dobb's Journal - December 2008 - Contents (Page 4) Dr. Dobb's Journal - December 2008 - Contents (Page 5) Dr. Dobb's Journal - December 2008 - Friday Night Fish Fry (Page 6) Dr. Dobb's Journal - December 2008 - Friday Night Fish Fry (Page 7) Dr. Dobb's Journal - December 2008 - Friday Night Fish Fry (Page 8) Dr. Dobb's Journal - December 2008 - Friday Night Fish Fry (Page 9) Dr. Dobb's Journal - December 2008 - Alia Vox (Page 10) Dr. Dobb's Journal - December 2008 - Alia Vox (Page 11) Dr. Dobb's Journal - December 2008 - Developer Diaries (Page 12) Dr. Dobb's Journal - December 2008 - Developer Diaries (Page 13) Dr. Dobb's Journal - December 2008 - Conversations (Page 14) Dr. Dobb's Journal - December 2008 - Conversations (Page 15) Dr. Dobb's Journal - December 2008 - The Man Who Sold the Sky (Page 16) Dr. Dobb's Journal - December 2008 - The Man Who Sold the Sky (Page 17) Dr. Dobb's Journal - December 2008 - The Man Who Sold the Sky (Page 18) Dr. Dobb's Journal - December 2008 - The Man Who Sold the Sky (Page 19) Dr. Dobb's Journal - December 2008 - Performance on Rails (Page 20) Dr. Dobb's Journal - December 2008 - Performance on Rails (Page 21) Dr. Dobb's Journal - December 2008 - Performance on Rails (Page 22) Dr. Dobb's Journal - December 2008 - Performance on Rails (Page 23) Dr. Dobb's Journal - December 2008 - Performance on Rails (Page 24) Dr. Dobb's Journal - December 2008 - Performance on Rails (Page 25) Dr. Dobb's Journal - December 2008 - Performance on Rails (Page 26) Dr. Dobb's Journal - December 2008 - Performance on Rails (Page 27) Dr. Dobb's Journal - December 2008 - Performance on Rails (Page 28) Dr. Dobb's Journal - December 2008 - LINQ-to-SQL and T-SQL (Page 29) Dr. Dobb's Journal - December 2008 - LINQ-to-SQL and T-SQL (Page 30) Dr. Dobb's Journal - December 2008 - LINQ-to-SQL and T-SQL (Page 31) Dr. Dobb's Journal - December 2008 - LINQ-to-SQL and T-SQL (Page 32) Dr. Dobb's Journal - December 2008 - LINQ-to-SQL and T-SQL (Page 33) Dr. Dobb's Journal - December 2008 - LINQ-to-SQL and T-SQL (Page 34) Dr. Dobb's Journal - December 2008 - A Remote Java RMI Registry (Page 35) Dr. Dobb's Journal - December 2008 - A Remote Java RMI Registry (Page 36) Dr. Dobb's Journal - December 2008 - A Remote Java RMI Registry (Page 37) Dr. Dobb's Journal - December 2008 - A Remote Java RMI Registry (Page 38) Dr. Dobb's Journal - December 2008 - A Remote Java RMI Registry (Page 39) Dr. Dobb's Journal - December 2008 - Beyond B-Trees (Page 40) Dr. Dobb's Journal - December 2008 - Beyond B-Trees (Page 41) Dr. Dobb's Journal - December 2008 - File Descriptors and Multithreaded Programs (Page 42) Dr. Dobb's Journal - December 2008 - File Descriptors and Multithreaded Programs (Page 43) Dr. Dobb's Journal - December 2008 - File Descriptors and Multithreaded Programs (Page 44) Dr. Dobb's Journal - December 2008 - File Descriptors and Multithreaded Programs (Page 45) Dr. Dobb's Journal - December 2008 - Effective Concurrency (Page 46) Dr. Dobb's Journal - December 2008 - Effective Concurrency (Page 47) Dr. Dobb's Journal - December 2008 - Effective Concurrency (Page 48) Dr. Dobb's Journal - December 2008 - The Agile Edge (Page 49) Dr. Dobb's Journal - December 2008 - The Agile Edge (Page 50) Dr. Dobb's Journal - December 2008 - The Agile Edge (Page 51) Dr. Dobb's Journal - December 2008 - Swaine's Flames (Page 52) Dr. Dobb's Journal - December 2008 - Swaine's Flames (Page Cover3) Dr. Dobb's Journal - December 2008 - Swaine's Flames (Page Cover4)
For optimal viewing of this digital publication, please enable JavaScript and then refresh the page. If you would like to try to load the digital publication without using Flash Player detection, please click here.