Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Otimização de Aplicações RoR

Otimização de Aplicações RoR

Gabriel Sobrinho

September 24, 2016
Tweet

More Decks by Gabriel Sobrinho

Other Decks in Programming

Transcript

  1. • Caching • Active Record • Lightning • Database View

    • Otimização de SQL • Fast Detect • Parse e dump de JSON • Parse e dump de XML • Gargabe Collector • Background Job • Assets • Planilhas • PDFs • CPU Bound Agenda
  2. There are only two hard things in Computer Science: cache

    invalidation and naming things. — Phil Karlton Caching
  3. class Cep def self.find(cep) cache_key = "cep/#{cep}" options = {

    expires_in: 7.days } Rails.cache.fetch(cache_key, options) do response = HTTPI.get(...) JSON.parse(response.body) end end end Rails.cache
  4. DICA • Configure um banco de caching como o memcached

    para ambientes distribuídos • Use somente se o tempo de processamento for maior que o tempo médio de rede do banco de caching • Cuidado com o dog-pile effect
  5. DICA • Use somente se a renderização for complexa (lenta)

    • Tempo médio de renderização deve ser maior que o tempo médio de rede • Russian Doll Caching
  6. DICA • Use somente se a resposta da requisição puder

    ser cacheada por completo • Existem técnicas para contornar essa limitação usando JavaScript • Procure sobre rack-etag e rack-cache
  7. DICA • Use a gem bullet para identificar N+1 e

    eager loads não utilizados • Ferramentas como NewRelic e Skylight monitoram esses problemas em produção
  8. DICA • Se o ambiente for distribuído a contagem nem

    sempre será perfeita (non thread-safe) • Se a contagem for importante, use trigger no banco de dados ou cache da view
  9. DICA • Não confie nos logs de tempo da instrumentação

    do rails • Evite consultas que retornam milhares de resultados com o Active Record
  10. • Técnica sugerida pela Brainspec • Ao invés de alocar

    objetos do active record, aloca apenas hashes puros • Ganho de pelo menos 30% no tempo de alocação Lightning
  11. DICA • Somente faz sentido se você não precisar da

    estrutura do active record • Compare o tempo de alocação, a tendência é o rails e ruby se tornarem mais rápidos com o passar do tempo
  12. select * from products where id = ?; select *

    from owners where id = ?; select * from categories where id = ?; select * from comments where product_id = ?; Database View
  13. create view products_report as select p.name AS product_name, o.name AS

    owner_name, c.name AS category_name, array_agg(cm.author) AS comment_authors, array_agg(cm.body) AS comment_bodies from products p join owners o on o.id = p.owner_id join categories c on c.id = p.category_id join comments ct on ct.product_id = p.id group by 1, 2, 3 Database View
  14. DICA • Reduz o tempo de rede consideravelmente • Recursos

    específicos do banco podem tornar difícil a migração para outra solução mas ninguém usa ORM para isso * • Use a gem scenic da Thoughtbot para facilitar o versionamento das views nas migrations
  15. create materialized view products_report as select p.name AS product_name, o.name

    AS owner_name, c.name AS category_name, array_agg(cm.author) AS comment_authors, array_agg(cm.body) AS comment_bodies from products p join owners o on o.id = p.owner_id join categories c on c.id = p.category_id join comments ct on ct.product_id = p.id group by 1, 2, 3 Materialised View
  16. -- locks the view refresh materialized view products_report; -- do

    not locks the view refresh materialized view concurrently products_report; Materialised View
  17. DICA • Recomputar a view pode ser feito por triggers

    no banco ou workers na aplicação • Índices podem otimizar ainda mais as consultas na materialized view • REFRESH sempre recomputa todos os registros, mesmo os sem modificação
  18. Seq Scan on people (cost=0.00..10174.39 rows=1 width=182) (actual time=13.330..48.007 rows=1

    loops=1) Filter: ((name)::text = 'GABRIEL CAMPOS SOBRINHO'::text) Rows Removed by Filter: 303630 Planning time: 0.114 ms Execution time: 48.077 ms EXPLAIN ANALYZE
  19. Index Scan using people_name on pessoas (cost=0.42..8.44 rows=1 width=182) (actual

    time=0.002..0.002 rows=1 loops=1) Index Cond: ((nome)::text = 'GABRIEL CAMPOS SOBRINHO'::text) Planning time: 0.261 ms Execution time: 0.021 ms EXPLAIN ANALYZE
  20. DICA • Todo banco possui uma forma de consultar o

    que está acontecendo na consulta • Estude a documentação do seu banco de dados para entender como otimizar • Se precisar de LIKE no Postgres, use o pg_trgm para indexar
  21. notes = Note.where(...) students.each do |student| disciplines.each do |discipline| student_note

    = notes.select do |note| note.student_id == student.id && note.discipline_id = discipline.id end end end Fast Detect
  22. data = Hash.new { |h, k| h[k] = {} }

    notes.each do |note| data[note.student_id][note.discipline_id] = note end Fast Detect
  23. DICA • Use para otimizar acessos repetitivos em longas coleções

    • Garanta que esse realmente seja o hotspot, na maioria das vezes o tempo de pesquisa não é significativo
  24. Benchmark.benchmark do |bm| bm.report 'to_json' do JSON.parse(JSON.dump(x)) end bm.report 'yajl'

    do Yajl::Parser.parse(Yajl::Encoder.encode(x)) end bm.report 'oj' do Oj.load(Oj.dump(x)) end end JSON
  25. DICA • Oj em média faz o mesmo processamento na

    metade do tempo • Oj possui uma implementação alternativa que pode ser até 20x mais rápida • Compatível apenas com MRI e Rubinius
  26. Benchmark.benchmark do |bm| bm.report 'nokogiri' do Nokogiri::XML(xml) end bm.report 'ox'

    do Ox.parse(xml) end bm.report 'libxml' do LibXML::XML::Document.string(xml) end end XML
  27. DICA • libxml em média faz o mesmo processamento na

    metade do tempo • libxml possui um preocupante histórico de vunerabilidades (nokogiri usa libxml) • ox se vende como mais seguro por não depender do libxml
  28. DICA • tunemygc.com monitora e tuna seu GC de acordo

    com a sua aplicação • Tenha certeza que o GC está sendo um problema na sua aplicação
  29. • Não otimiza a aplicação mas libera o application server

    para continuar atendendo requisições • Cuidado com gargalos no banco de dados • Sidekiq é a opção mais utilizada mas não é a mais robusta Background Job
  30. • Utilize o asset pipeline ou webpack • Compacte os

    assets com minificação e gzip • CDNs podem reduzir o tempo de rede consideravelmente Assets
  31. • Prefira CSV ao invés de XLSX • Se for

    necessário criar fórmulas e gráficos, utilize o axls • Se a planilha for complexa, prefira gerar em background job Planilhas
  32. • wkhtmltopdf consome muito recurso (processamento e memória) • prawn

    e prawn-table costuma ser a melhor opção • Alternativas incluem usar o jRuby com bibliotecas java ou integração com o iReport + Jasper Reports PDF
  33. • Ruby nem sempre é a melhor opção para CPU

    Bound • Procure sobre Crystal, Rust, C e similares para integrar usando a gem FFI CPU Bound
  34. RESUMO • Sempre faça profiling para ter certeza sobre o

    que está lento na sua aplicação • Seu problema de performance provavelmente já foi resolvido por alguém
  35. RESUMO • Não tenha medo de usar os recursos do

    banco de dados, é o principal ponto de otimização na maioria das aplicações • Compartilhe conhecimento sempre que criar ou aprender uma nova técnica