Boosting the performance of       your Rails apps      Jakarta RoR meetup, January 2013               Matt Kuklinski      ...
Where to start?• There are many ways to improve RoR app  speed – some easy, some hard• Concentrate on methods that give yo...
1. DB Indexes• Your app will be constrained by database  performance• Appropriate DB indexes can give you 100x  performanc...
Example with indexCREATE INDEXaddresses_addressable_id_addressable_type_idxON addresses USING btree(addressable_id, addres...
Now without the indexDROP INDEXaddresses_addressable_id_addressable_type_idx;t1 =   Time.now c =   Company.find(178389) a ...
Index Tips• add indexes to all referential attributes,  and other attributes that you regularly  search or sort over if th...
2. Minimise amount of DB queries• RoR makes it easy to program quickly• The downside:  RoR makes it easy for the number of...
Example: N+1 queries• Let’s say we have a Client model, and each Client can  have one or more industries through ClientInd...
Be careful doing this:# app/controllers/clients_controller.rbdef index    @clients = Client.allendIf you have 50 clients, ...
Solution: Eager Loading# app/controllers/clients_controller.rbdef index    @clients = Client.includes(:industries).allendN...
3. Minimise memory usage• Only use gems that you actually need• Don’t load objects into memory unless  you need to use the...
Example: find_eachAn example using real data:Using find:t1 = Time.nowCompany.where(:country_id=>1).find do |c|  puts "do s...
Now using find_each:t1 = Time.nowCompany.where(:country_id=>1).find_each do |c|  puts "do something!" if [MattskiTest].inc...
3. Caching• Can make a huge difference to  performance• Lots of options:  – page caching  – action caching  – fragment cac...
What is Memcached?• Free & open source, high-performance,  distributed memory object caching system.• Memcached is an in-m...
What is Redis?• Redis is an open source, advanced key-  value store. It is often referred to as a  data structure server s...
Pre-calculate summary dataExample:• We have tables holding sales, sales_reps and teams• We need to provide live monthly an...
Solution:• Summarise the data in a sales_metrics table with good indexes and  use observers and delayed_jobs to recalculat...
6. Make web requests fast• You have a limited number of processes  available to serve web requests, so they  need to be fa...
Solution: use background processes• Use background processes such as delayed jobs for long-running  jobs. This will free y...
7. Monitor performance• Make sure to monitor the performance of  your apps so that you can pinpoint which  areas are runni...
What does this tell me?
- Response time is good.     - There’s no request queuing.- I can scale back the web processes
What does this tell me?
-   Performance is not that great    -  The database is being overworked-   There may be some inefficient DB queries
The slowness is almost entirely caused by theSearchController. This is a target for optimisation.
8. Use an in-memory DB• Databases are fast when the searching  and sorting is done in memory• They slow down a lot when th...
Solution: keep your DB trim• Try to limit the size of the DB so that it  fits entirely within memory• Move non-essential i...
9. Manage your load• load balancing  – essential for public web apps  – Cloud hosting providers help to manage this for   ...
More performance tips• Use a content distribution network for static  files.  – AWS CloudFront, etc.• Make the UI asynchro...
Thanks for your interest!• Contact  – www.linkedin.com/in/matthewkuklinski  – @mattkuklinski  – www.gopher.co.id  – www.go...
Upcoming SlideShare
Loading in …5
×

Boosting the Performance of your Rails Apps

12,434 views

Published on

A short presentation showing some ways to improve the performance of ruby on rails apps. Presented at the Jakarta ruby user group meetup.

Published in: Technology
1 Comment
29 Likes
Statistics
Notes
No Downloads
Views
Total views
12,434
On SlideShare
0
From Embeds
0
Number of Embeds
296
Actions
Shares
0
Downloads
112
Comments
1
Likes
29
Embeds 0
No embeds

No notes for slide

Boosting the Performance of your Rails Apps

  1. 1. Boosting the performance of your Rails apps Jakarta RoR meetup, January 2013 Matt Kuklinski CTO, Gopher
  2. 2. Where to start?• There are many ways to improve RoR app speed – some easy, some hard• Concentrate on methods that give you the biggest bang for the buck• This presentation shows a few different methods that should give you a good performance return for your time investment
  3. 3. 1. DB Indexes• Your app will be constrained by database performance• Appropriate DB indexes can give you 100x performance gains on large tables• Not all rails developers realise how important this is• It’s easy to add indexes:class AddIndexToClientIndustry < ActiveRecord::Migration def change add_index :client_industries, :client_id endend
  4. 4. Example with indexCREATE INDEXaddresses_addressable_id_addressable_type_idxON addresses USING btree(addressable_id, addressable_type);t1 = Time.now c = Company.find(178389) a = c.addresses.firstt2 = Time.nowputs "---Operation took #{t2-t1} seconds---”Result with index:---Operation took 0.012412 seconds---
  5. 5. Now without the indexDROP INDEXaddresses_addressable_id_addressable_type_idx;t1 = Time.now c = Company.find(178389) a = c.addresses.firstt2 = Time.nowputs "---Operation took #{t2-t1} seconds---”Result without index:---Operation took 0.378073 seconds---0.378073 / 0.012412 = 30.46 times slower without the index
  6. 6. Index Tips• add indexes to all referential attributes, and other attributes that you regularly search or sort over if they contain a lot of distinct values• dont add too many indexes - each one increases the DB size and reduces the performance of insert and update queries
  7. 7. 2. Minimise amount of DB queries• RoR makes it easy to program quickly• The downside: RoR makes it easy for the number of database queries per request to explode out of control
  8. 8. Example: N+1 queries• Let’s say we have a Client model, and each Client can have one or more industries through ClientIndustry.• We want to show a list of clients, and their primary industries:<% @clients.each do |client| %> <tr> <td><%= client.id %></td> <td><%= client.business_name %></td> <td><%= client.industries.first.name %></td> </tr><% end %>
  9. 9. Be careful doing this:# app/controllers/clients_controller.rbdef index @clients = Client.allendIf you have 50 clients, then 51 DB queries will be run:Processing by ClientsController#index as HTMLSELECT "clients".* FROM "clients"SELECT "industries".* FROM "industries" INNER JOIN "client_industries" ON"industries"."id" = "client_industries"."industry_id" WHERE"client_industries"."client_id" = 1 LIMIT 1SELECT "industries".* FROM "industries" INNER JOIN "client_industries" ON"industries"."id" = "client_industries"."industry_id" WHERE"client_industries"."client_id" = 2 LIMIT 1SELECT "industries".* FROM "industries" INNER JOIN "client_industries" ON"industries"."id" = "client_industries"."industry_id" WHERE"client_industries"."client_id" = 3 LIMIT 1…
  10. 10. Solution: Eager Loading# app/controllers/clients_controller.rbdef index @clients = Client.includes(:industries).allendNow just 2 or 3 queries are performed instead of 51Processing by ClientsController#index as HTMLSELECT "clients".* FROM "clients"SELECT "client_industries".* FROM "client_industries" WHERE"client_industries"."client_id" IN (1, 2, 3)SELECT "industries".* FROM "industries" WHERE "industries"."id" IN (1, 5,7, 8, 4)
  11. 11. 3. Minimise memory usage• Only use gems that you actually need• Don’t load objects into memory unless you need to use them• When processing massive datasets, split them into batches
  12. 12. Example: find_eachAn example using real data:Using find:t1 = Time.nowCompany.where(:country_id=>1).find do |c| puts "do something!" if [MattskiTest].include?(c.common_name)endt2 = Time.nowputs "---Operation took #{t2-t1} seconds---”Result:1 query, taking 46.65 seconds
  13. 13. Now using find_each:t1 = Time.nowCompany.where(:country_id=>1).find_each do |c| puts "do something!" if [MattskiTest].include?(c.common_name)endt2 = Time.nowputs "---Operation took #{t2-t1} seconds---"Result:>100 queries, taking 15.53 seconds in total (3x faster)Sometimes more queries isbetter!
  14. 14. 3. Caching• Can make a huge difference to performance• Lots of options: – page caching – action caching – fragment caching – Memcached, Redis• Tip: get your data model correct first. Caching can hide structural problems
  15. 15. What is Memcached?• Free & open source, high-performance, distributed memory object caching system.• Memcached is an in-memory key-value store for small chunks of arbitrary data (strings, objects) from results of database calls, API calls, or page rendering.• www.memcached.org
  16. 16. What is Redis?• Redis is an open source, advanced key- value store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets and sorted sets.• redis.io
  17. 17. Pre-calculate summary dataExample:• We have tables holding sales, sales_reps and teams• We need to provide live monthly and daily charts showing cumulative sales per sales_rep, per team, and for the company as a whole• We could produce a complicated ruby method that results in a query like this: select date(sales.created_at) as sale_date ,sales_reps.name ,sum(sales.amount) as daily_sales from sales join sales_reps on sales_reps.id = sales.sales_rep_id where sales.created_at > 2013-01-01’ group by 1,2;But that’s not very efficient if we have 300 sales reps and managerschecking all their charts every few minutes. How can we speed it up?
  18. 18. Solution:• Summarise the data in a sales_metrics table with good indexes and use observers and delayed_jobs to recalculate the sales data in near-real time.• Then we can do:sales_rep.sales_metrics.where(:date>2013-01-01)To get an optimised query like this:select date ,sales_rep_id ,daily_sales from sales_metrics where sales_metrics.date >= 2013-01-01’Now instead of 300 sales reps, imagine having 20,000 daytraderschecking their daily stock portfolio charts… It has to be pre-calculated.
  19. 19. 6. Make web requests fast• You have a limited number of processes available to serve web requests, so they need to be fast• Ideally, web processes should finish within milliseconds. 1-2 seconds is slow. 10+ seconds is very slow.• If you have slow web requests then your rails app wont be able to support many simultaneous users.
  20. 20. Solution: use background processes• Use background processes such as delayed jobs for long-running jobs. This will free your web processes up to handle more requests.• What types of things? – sending email – running reports – processing images – obtaining information from third party APIs• Suggestion: use priorities so that important background processes get actioned before less important ones if there is a build up in jobs• Note: Rails 4 will support background processing out of the box
  21. 21. 7. Monitor performance• Make sure to monitor the performance of your apps so that you can pinpoint which areas are running slowly.• New Relic is an excellent tool for monitor rails apps
  22. 22. What does this tell me?
  23. 23. - Response time is good. - There’s no request queuing.- I can scale back the web processes
  24. 24. What does this tell me?
  25. 25. - Performance is not that great - The database is being overworked- There may be some inefficient DB queries
  26. 26. The slowness is almost entirely caused by theSearchController. This is a target for optimisation.
  27. 27. 8. Use an in-memory DB• Databases are fast when the searching and sorting is done in memory• They slow down a lot when they have to go to disk
  28. 28. Solution: keep your DB trim• Try to limit the size of the DB so that it fits entirely within memory• Move non-essential information out of the main DB into a secondary DB or elsewhere (i.e. audit logs, inactive accounts, old email logs, etc.)• Consider using non-relational databases if you have massive storage requirements
  29. 29. 9. Manage your load• load balancing – essential for public web apps – Cloud hosting providers help to manage this for you• database replication – run heavy reports on a replicated database so that the performance of the main database isn’t affected• database sharding
  30. 30. More performance tips• Use a content distribution network for static files. – AWS CloudFront, etc.• Make the UI asynchronous – use AJAX lazy loading for anything that takes more than 1-2 seconds to load• Use a Service Oriented Architecture, so that some actions can be done in parallel on a different hosting stack
  31. 31. Thanks for your interest!• Contact – www.linkedin.com/in/matthewkuklinski – @mattkuklinski – www.gopher.co.id – www.gopher.co.nz

×