31
Boosting the performance of your Rails apps Jakarta RoR meetup, January 2013 Matt Kuklinski CTO, Gopher

Boosting the Performance of your Rails Apps

Embed Size (px)

DESCRIPTION

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

Citation preview

Page 1: Boosting the Performance of your Rails Apps

Boosting the performance of your Rails apps

Jakarta RoR meetup, January 2013

Matt KuklinskiCTO, Gopher

Page 2: Boosting the Performance of your Rails Apps

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

Page 3: Boosting the Performance of your Rails Apps

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

Page 4: Boosting the Performance of your Rails Apps

Example with index

CREATE INDEX addresses_addressable_id_addressable_type_idx ON 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---

Page 5: Boosting the Performance of your Rails Apps

Now without the index

DROP INDEX addresses_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

Page 6: Boosting the Performance of your Rails Apps

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

• don't add too many indexes - each one increases the DB size and reduces the performance of insert and update queries

Page 7: Boosting the Performance of your Rails Apps

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

Page 8: Boosting the Performance of your Rails Apps

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 %>

Page 9: Boosting the Performance of your Rails Apps

Be careful doing this:

# app/controllers/clients_controller.rbdef index @clients = Client.allend

If 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…

Page 10: Boosting the Performance of your Rails Apps

Solution: Eager Loading# app/controllers/clients_controller.rbdef index @clients = Client.includes(:industries).allend

Now just 2 or 3 queries are performed instead of 51

Processing 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)

Page 11: Boosting the Performance of your Rails Apps

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

Page 12: Boosting the Performance of your Rails Apps

Example: find_each

An example using real data:

Using find:t1 = Time.now Company.where(:country_id=>1).find do |c| puts "do something!" if ['Mattski Test'].include?(c.common_name)endt2 = Time.nowputs "---Operation took #{t2-t1} seconds---”

Result:1 query, taking 46.65 seconds

Page 13: Boosting the Performance of your Rails Apps

Now using find_each:t1 = Time.now Company.where(:country_id=>1).find_each do |c| puts "do something!" if ['Mattski Test'].include?(c.common_name)end t2 = Time.nowputs "---Operation took #{t2-t1} seconds---"

Result:>100 queries, taking 15.53 seconds in total (3x faster)

Sometimes more queries is better!

Page 14: Boosting the Performance of your Rails Apps

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

Page 15: Boosting the Performance of your Rails Apps

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

Page 16: Boosting the Performance of your Rails Apps

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

Page 17: Boosting the Performance of your Rails Apps

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 managers checking all their charts every few minutes. How can we speed it up?

Page 18: Boosting the Performance of your Rails Apps

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 daytraders checking their daily stock portfolio charts… It has to be pre-calculated.

Page 19: Boosting the Performance of your Rails Apps

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.

Page 20: Boosting the Performance of your Rails Apps

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

Page 21: Boosting the Performance of your Rails Apps

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

Page 22: Boosting the Performance of your Rails Apps

What does this tell me?

Page 23: Boosting the Performance of your Rails Apps

- Response time is good.- There’s no request queuing.

- I can scale back the web processes

Page 24: Boosting the Performance of your Rails Apps

What does this tell me?

Page 25: Boosting the Performance of your Rails Apps

- Performance is not that great- The database is being overworked

- There may be some inefficient DB queries

Page 26: Boosting the Performance of your Rails Apps

The slowness is almost entirely caused by the SearchController. This is a target for optimisation.

Page 27: Boosting the Performance of your Rails Apps

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

Page 28: Boosting the Performance of your Rails Apps

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

Page 29: Boosting the Performance of your Rails Apps

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

Page 30: Boosting the Performance of your Rails Apps

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

Page 31: Boosting the Performance of your Rails Apps

Thanks for your interest!

• Contact– www.linkedin.com/in/matthewkuklinski–@mattkuklinski

– www.gopher.co.id– www.gopher.co.nz