Intro to-rails-webperf

Post on 19-Jan-2015

244 views 0 download

Tags:

description

 

transcript

Intro to Rails #webperf

@amateurhuman

1.9.2

1.8.7

1.9.3

1.8.6

ruby -v

MRI 1.9.3

MRI 1.8.7

require ‘benchmark’

RBX 1.2.4

JRuby 1.6.7

Passengers,Unicorns,Pumas.Oh my!

PassengerSimple to operate.Simple configuration.Handles worker management.Great for multi-app environments.Great for low resource environments.Attached to Nginx/Apache HTTPD.

Unicorn

Highly configurable.Independent of front-end web server.Master will reap children on timeout.Great for single app environments.Allows for zero downtime deploys.

Puma

Based on Mongrel.Designed for concurrency.Uses real threads.

Anatomy of a Web Request

Redirects Cache DNS TCP SSL Request App Response DOM Render

Link Clicked

First Byte

DOM Loaded

Load

80% in Front-End

Inside the Web App

Database Performance

Lazy Loading๏ ORMs make it easy to access data.๏ Easy access to data can create issues.๏ Performance issues are hard to see in

development mode.๏ Look to production metrics to optimize

and refactor.

N+1 Query Creep# app/models/customer.rbclass Customer < ActiveRecord::Base has_many :addressesend

# app/models/address.rbclass Address < ActiveRecord::Base belongs_to :customerend

# app/controllers/customers_controller.rbclass CustomersController < ApplicationController def index @customers = Customer.all endend

# app/views/customers/index.html.erb<% @customers.each do |customer| %> <%= content_tag :h1, customer.name %><% end %>

N+1 Query Creep# app/views/customers/index.html.erb<% @customers.each do |customer| %> <%= content_tag :h1, customer.name %> <%= content_tag :h2, customer.addresses.first.city %><% end %>

If @customers has 100 records, you'll have 101 queries:SELECT "customers".* FROM "customers"SELECT "addresses".* FROM "addresses" WHERE "addresses"."customer_id" = 1 AND

"addresses"."primary" = 't' LIMIT 1SELECT "addresses".* FROM "addresses" WHERE "addresses"."customer_id" = 2 AND

"addresses"."primary" = 't' LIMIT 1SELECT "addresses".* FROM "addresses" WHERE "addresses"."customer_id" = 3 AND

"addresses"."primary" = 't' LIMIT 1......SELECT "addresses".* FROM "addresses" WHERE "addresses"."customer_id" = 100

AND "addresses"."primary" = 't' LIMIT 1

Eager Load Instead# app/controllers/customers_controller.rbclass CustomersController < ApplicationController def index @customers = Customer.includes(:addresses).all endend

If @customers has 100 records, now we only have 2 queries:SELECT "customers".* FROM "customers" SELECT "addresses".* FROM "addresses" WHERE "addresses"."customer_id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100)

Finding N+1

Missing Indexes๏ Searching a 1,000 row table with an

index is 100x faster than searching without.

๏ Put indexes anywhere you might need to query; less is not more with indexes.

๏ Writing an index will lock your tables.

Missing Indexes Hurt

Indexes are Easy# db/migrate/20120201040247_add_index_for_shop_id_on_orders.rb

class AddIndexForShopIdOnOrders < ActiveRecord::Migration def change add_index :orders, :shop_id endend

Cache All The Things

Page Caching# app/controllers/products_controller.rbclass ProductsController < ActionController caches_page :index def index @products = Products.all end def create expire_page :action => :index end end

# /opt/nginx/conf/nginx.conflocation / { gzip_static on;}

Action Caching# app/controllers/products_controller.rbclass ProductsController < ActionController before_filter :authenticate caches_action :index def index @products = Product.all end def create expire_action :action => :index end end

Fragment Caching# app/views/products/index.html.erb

<% Order.find_recent.each do |o| %> <%= o.buyer.name %> bought <%= o.product.name %><% end %> <% cache(‘all_products’) do %> All available products: <% Product.all.each do |p| %> <%= link_to p.name, product_url(p) %> <% end %><% end %>

# app/controllers/products_controller.rbclass ProductsController < ActionController def update expire_fragment(‘all_products’) end

end

Expiring Cachesis Hard

Russian Doll Caching# app/views/products/show.html.erb<% cache product do %> Product options: <%= render product.options %><% end %>

# app/views/options/_option.html.erb<% cache option do %> <%= option.name %> <%= option.description %><% end %>

# app/models/product.rbclass Product < ActiveRecord::Base has_many :optionsend

# app/models/option.rbclass Option < ActiveRecord::Base belongs_to :product, touch: trueend

Background Jobs

ProcrastinateReporting.Sending email.Processing images.Call external services.Building & Expiring Caches.

Rescued by Resqueclass ReferralProcessor @queue = :referrals_queue

def self.perform(schema_name, order_item_id) order_item = OrderItem.find(order_item_id) order = order_item.order user = order.user credit = AccountCredit.credit(order_item.unit_price, user, 'referral') credit.message = I18n.t('account_credits.predefined_messages.referral', :description => order_item.description) credit.save! debit = Transaction.account_debit(credit.amount, user) debit.order = order debit.save! order.issue_refund(return_to_inventory: false, gateway_first: true, cancel_items: false, cancel_certificates: false, amount: credit.amount, as: 'original', notify_user: false) if user.receives_mail_for?(:referral_purchase) SystemMailer.referral_refund(order_item, credit).deliver end endend

Get in Lineclass ReferralObserver < ActiveRecord::Observer

def after_create(referral) Resque.enqueue_in(1.day, ReferralProcessor, referral.item.id) end

end

# Get it started$ PIDFILE=./resque.pid \ BACKGROUND=yes \ QUEUE=referrals_queue \ rake environment resque:work

It’s Free in Rails 4

Establishing basic Queue API.Implement push and pop.Easily swap out for Resque, Sidekiq, Delayed job.

What’s One More Second?

7%Fewer Conversions

11%Fewer Page Views

Time isMoney

Monitor your applications.

Performance is not set it and forget it.

Database indexes are cheap, make more.

Cache something, somewhere.

Push work off to the background.

Don’t neglect front-end performance.

30-day free trial atnewrelic.com/30

Q?