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

Casos de otimização em aplicações Ruby on Rails

Casos de otimização em aplicações Ruby on Rails

Gabriel Sobrinho

July 14, 2018
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 • A biblioteca goldiloader resolve a maioria sozinho
  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 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. 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
  17. -- locks the view refresh materialized view products_report; -- does

    not lock the view refresh materialized view concurrently products_report; Materialised View
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. data = Hash.new { |h, k| h[k] = {} }

    notes.each do |note| data[note.student_id][note.discipline_id] = note end Fast Detect
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. • 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
  31. • Utilize o asset pipeline ou webpack • Compacte os

    assets com minificação e gzip • CDNs podem reduzir o tempo de rede consideravelmente Assets
  32. • 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
  33. • 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
  34. • 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
  35. 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
  36. 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