Otimizando Aplicações em Rails

      essa palestra será rápida
 Servidor
Active Record / SQL
Ruby / Rails
Cache
BackgrounDRb
Metal
HTML, JS & CSS
Nginx, Apache

balanceadores de carga
joeandmotorboat.com/2008/02/28/apache-vs-nginx-web-server-performance-deathmatch/
Mongrel, Thin, Passenger

    servidores de aplicação
http://izumi.plan99.net/blog/index.php/2008/03/31/benchmark-passenger-mod_rails-vs-mongrel-vs-thin/
Active Record / SQL
INCLUDE


for list in List.all
  ... do some ...
  for contact in list.contacts
    ... do some ...
  end
end
INCLUDE


for list in List.all(:include => :contacts)
  ... do some ...
  for contacts in list.contacts
    ... do some ...
  end
end
ÍNDICES



Rails não adiciona índices nas
tabelas
EXPLAIN ANALYZE SELECT * FROM contacts WHERE
email = 'foo@bar.com';>> cost=0.00..25.38CREATE INDEX
contacts_email_idx    ON contacts(email);>> cost=0.00..1.06
TABELAS CONSOLIDADAS



Newsletter#history
>> Buscava dados de mais de 100 mensagens (total de envios,
acessos, cliques, descadastros, informações geográficas...) em uma
view e apresentava um gráfico.

Tempo médio do método: 18s
 
CREATE TABLE messages_complete_history AS SELECT *
FROM view_messages_complete_history;

Tempo médio do método: 0.3s
 
>> Criação agendada para a madrugada com BackgroundRB
 
CSV EXPORT


COPY (SELECT * FROM contacts) TO 'path' WITH
DELIMITER ';' CSV HEADER


Muitas ordens de magnitude mais
rápido do que AR + FasterCSV
WORKING ON



postgres_timestamps plugin
   sobrescreve comportamento de created_at e updated_at do
   AR
   cria defaults e triggers automaticamente nas migrações
   benchmarks que fiz: de 2 a 5% mais rápido em 1000 inserts.
   até 10% mais rápido em 1000 updates;

                                
>> Prometo para ainda este ano!
 
 
Ruby / Rails
BENCHMARK


require 'benchmark'
 
Benchmark.bm do |x|
x.report('foo') { ... code ... }
x.report('bar') { ... code ... }
end
||=


def calculate_something
  @something = do_some_heavy_calculation
end
MEMOIZE


def calculate_something(*args)
  do_some_heavy_calculation
end
memoize :calculate_something

calculate_something(1)   #Hit
calculate_something(1)   #Cache
calculate_something(1)   #Cache
calculate_something(2)   #Hit
RAILS STARTUP


# environment startup file

require 'geoip_city'
GEOIPDB = GeoIPCity::Database.new('file.dat')

# some controller or model
GEOIPDB.look_up(ip)
DICAS GERAIS


ids = @contacts.map(&:id)     #bad
ids = @contacts.map{|c| c.id} #ugly, but good

str = str.gsub(/java/, 'ruby')#bad
str.gsub!(/java/, 'ruby')     #good
Cache
PAGE CACHE


# site controller
caches_page :index
ACTION CACHE


# login controller
before_filter :get_client
caches_action :index
FRAGMENT CACHE


# some view
- cache :action_suffix => 'comments' do
  = render :partial => 'comments'

# no controller...
def create_comment
  ...
  expire_fragment :action => 'show_post',
                :action_suffix => 'comments'
end
FRAGMENT CACHE


# some view
- cache :key => @contact.updated_at do
  = render :partial => 'contact_info'
# Expira automático, mas gera alguns arquivos
...
HTTP CLIENT CACHE


fresh_when 
   :last_modified => @product.published_at.utc, 
   :etag => @article
CACHE-MONEY PLUGIN


               http://github.com/nkallen/
                cache-money/tree/master
 
 
>> Try it out and tell us...
 
Metal
METAL


class Go
  def self.call(env)
    if env["PATH_INFO"] =~ /^/go/view/(d+)/
      id = $1
      request = Rack::Request.new(env)
      delivery = Delivery.find id
      View.create :contact_id => delivery.
contact_id, :message_id => delivery.message_id, :ip
=> request.ip
      delivery.contact.confirm!
      [200, {"Content-Type" => "text/html"}, [""]]
    else
      [404, {"Content-Type" => "text/html"}, ["Not
Found"]]
    end
  end
end
METAL




2-5x faster
>> NÃO DEVERIA SE CHAMAR SPEED METAL?
BackgrounDRb
Pode executar tarefas em bg;
Pode agendar tarefas para
executar em bg;
Pode executar tarefas
periodicamente em bg;
Pode até conversar via TCP;
EXECUTANDO UMA TAREFA


class ContactsWorker < BackgrounDRb::MetaWorker
 set_worker_name :contacts_worker
 def delete(args)
  total = args[:contacts].size
  cache[:total] = total
  cache[:curr] = 0
  cache[:msg] = Contact.delete_many(args[:contacts]) do
     cache[:curr] += 1
  end
 end
end

# start worker on controller
MiddleMan.worker(:contacts_worker).async_delete(:args =>
{:contacts => @contacts}, :job_key => @job_key)

# verify worker status via Ajax
t = MiddleMan.worker(:contacts_worker).ask_result(:total)
c = MiddleMan.worker(:contacts_worker).ask_result(:curr)
return render :json => [t, c]
EXECUTANDO UMA TAREFA



Média antes: 13s (muitas FKs!
milhares de contatos!)
Média depois: 0.1s (usuário pode
trabalhar durante processo)
EXECUTANDO UMA TAREFA AGENDADA


MiddleMan.worker(:messenger_worker)
.enq_send_messages(
    :args => @message.id,
    :scheduled_at => @message.scheduled_at,
    :job_key => "message_#{@message.id}")
EXECUTANDO UMA TAREFA PERIÓDICA


# backgroundrb.yml
:schedules:
  :table_cache_worker:
    :table_cache:
      :trigger_args: 0 30 4 * * * *
HTML, JS & CSS
CSS         PLEASE DONT USE FONT TAG


       SPRITES *
         JSMIN
          GZIP
HTTP CLIENT CACHE (304!)
return render :nothing => true, :status => 304
Cloud Storage e CDN

    static.mailee.me

Otimizando Aplicações em Rails

  • 1.
    Otimizando Aplicações emRails essa palestra será rápida
  • 2.
     Servidor Active Record /SQL Ruby / Rails Cache BackgrounDRb Metal HTML, JS & CSS
  • 4.
  • 5.
  • 6.
    Mongrel, Thin, Passenger servidores de aplicação
  • 7.
  • 8.
  • 9.
    INCLUDE for list inList.all ... do some ... for contact in list.contacts ... do some ... end end
  • 10.
    INCLUDE for list inList.all(:include => :contacts) ... do some ... for contacts in list.contacts ... do some ... end end
  • 11.
    ÍNDICES Rails não adicionaíndices nas tabelas EXPLAIN ANALYZE SELECT * FROM contacts WHERE email = 'foo@bar.com';>> cost=0.00..25.38CREATE INDEX contacts_email_idx    ON contacts(email);>> cost=0.00..1.06
  • 12.
    TABELAS CONSOLIDADAS Newsletter#history >> Buscavadados de mais de 100 mensagens (total de envios, acessos, cliques, descadastros, informações geográficas...) em uma view e apresentava um gráfico. Tempo médio do método: 18s   CREATE TABLE messages_complete_history AS SELECT * FROM view_messages_complete_history; Tempo médio do método: 0.3s   >> Criação agendada para a madrugada com BackgroundRB  
  • 13.
    CSV EXPORT COPY (SELECT* FROM contacts) TO 'path' WITH DELIMITER ';' CSV HEADER Muitas ordens de magnitude mais rápido do que AR + FasterCSV
  • 14.
    WORKING ON postgres_timestamps plugin sobrescreve comportamento de created_at e updated_at do AR cria defaults e triggers automaticamente nas migrações benchmarks que fiz: de 2 a 5% mais rápido em 1000 inserts. até 10% mais rápido em 1000 updates;   >> Prometo para ainda este ano!    
  • 15.
  • 16.
    BENCHMARK require 'benchmark'   Benchmark.bm do|x| x.report('foo') { ... code ... } x.report('bar') { ... code ... } end
  • 17.
    ||= def calculate_something @something = do_some_heavy_calculation end
  • 18.
    MEMOIZE def calculate_something(*args) do_some_heavy_calculation end memoize :calculate_something calculate_something(1) #Hit calculate_something(1) #Cache calculate_something(1) #Cache calculate_something(2) #Hit
  • 19.
    RAILS STARTUP # environmentstartup file require 'geoip_city' GEOIPDB = GeoIPCity::Database.new('file.dat') # some controller or model GEOIPDB.look_up(ip)
  • 20.
    DICAS GERAIS ids =@contacts.map(&:id) #bad ids = @contacts.map{|c| c.id} #ugly, but good str = str.gsub(/java/, 'ruby')#bad str.gsub!(/java/, 'ruby') #good
  • 21.
  • 22.
    PAGE CACHE # sitecontroller caches_page :index
  • 23.
    ACTION CACHE # logincontroller before_filter :get_client caches_action :index
  • 24.
    FRAGMENT CACHE # someview - cache :action_suffix => 'comments' do = render :partial => 'comments' # no controller... def create_comment ... expire_fragment :action => 'show_post', :action_suffix => 'comments' end
  • 25.
    FRAGMENT CACHE # someview - cache :key => @contact.updated_at do = render :partial => 'contact_info' # Expira automático, mas gera alguns arquivos ...
  • 26.
    HTTP CLIENT CACHE fresh_when     :last_modified=> @product.published_at.utc,     :etag => @article
  • 27.
    CACHE-MONEY PLUGIN http://github.com/nkallen/ cache-money/tree/master     >> Try it out and tell us...  
  • 28.
  • 29.
    METAL class Go def self.call(env) if env["PATH_INFO"] =~ /^/go/view/(d+)/ id = $1 request = Rack::Request.new(env) delivery = Delivery.find id View.create :contact_id => delivery. contact_id, :message_id => delivery.message_id, :ip => request.ip delivery.contact.confirm! [200, {"Content-Type" => "text/html"}, [""]] else [404, {"Content-Type" => "text/html"}, ["Not Found"]] end end end
  • 30.
    METAL 2-5x faster >> NÃODEVERIA SE CHAMAR SPEED METAL?
  • 31.
  • 32.
    Pode executar tarefasem bg; Pode agendar tarefas para executar em bg; Pode executar tarefas periodicamente em bg; Pode até conversar via TCP;
  • 33.
    EXECUTANDO UMA TAREFA classContactsWorker < BackgrounDRb::MetaWorker set_worker_name :contacts_worker def delete(args) total = args[:contacts].size cache[:total] = total cache[:curr] = 0 cache[:msg] = Contact.delete_many(args[:contacts]) do cache[:curr] += 1 end end end # start worker on controller MiddleMan.worker(:contacts_worker).async_delete(:args => {:contacts => @contacts}, :job_key => @job_key) # verify worker status via Ajax t = MiddleMan.worker(:contacts_worker).ask_result(:total) c = MiddleMan.worker(:contacts_worker).ask_result(:curr) return render :json => [t, c]
  • 34.
    EXECUTANDO UMA TAREFA Médiaantes: 13s (muitas FKs! milhares de contatos!) Média depois: 0.1s (usuário pode trabalhar durante processo)
  • 35.
    EXECUTANDO UMA TAREFAAGENDADA MiddleMan.worker(:messenger_worker) .enq_send_messages( :args => @message.id, :scheduled_at => @message.scheduled_at, :job_key => "message_#{@message.id}")
  • 36.
    EXECUTANDO UMA TAREFAPERIÓDICA # backgroundrb.yml :schedules: :table_cache_worker: :table_cache: :trigger_args: 0 30 4 * * * *
  • 37.
  • 38.
    CSS PLEASE DONT USE FONT TAG SPRITES * JSMIN GZIP HTTP CLIENT CACHE (304!) return render :nothing => true, :status => 304
  • 39.
    Cloud Storage eCDN static.mailee.me