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

Performance - Vamos dormir mais @ 1º Tech Day GURU-PR

Performance - Vamos dormir mais @ 1º Tech Day GURU-PR

Praticamente toda startup é focada em resultado rápido e como efeito colateral, ao atingir sucesso, ela sofre com performance.
Essa apresentação lista diversas melhorias de performance não muito difundidas que visa dar mais tranquilidade ao dia-a-dia dos desenvolvedores.

A8fd9392c7ee3d1ffc29c6a1465ae6f3?s=128

Marcelo Cajueiro

August 10, 2015
Tweet

Transcript

  1. Performance - vamos dormir mais!

  2. Marcelo Cajueiro — 5 anos trabalhando com Ruby on Rails

    — 2 anos no — Sempre remotamente — Não toco nenhum instrumento — Chat da uol: gato_tatuado_na_cam — about.me/marcelocajueiro
  3. Disclaimer

  4. None
  5. Relógio

  6. Barba + Relógio = Maior confiança — Fonte: Broscience (papo

    de homem)
  7. Tarot Da Startup

  8. None
  9. None
  10. None
  11. None
  12. None
  13. None
  14. None
  15. None
  16. None
  17. None
  18. None
  19. None
  20. None
  21. None
  22. None
  23. None
  24. Fim.

  25. None
  26. Réplica do banco de dados para consultas

  27. gem 'octopus'

  28. class Badge < ActiveRecord::Base replicated_model end @user = User.using(:shard1).find_by_name("Joao")

  29. Sidekiq: usando réplica para emails Sidekiq::Extensions::DelayedMailer. sidekiq_options queue: :read_only_default Procfile

    DATABASE_URL=$DATABASE_FOLLOWER_URL sidekiq -c 10 -q read_only_default,2
  30. Queries N+1 http://goo.gl/2j191w 1 1 http://blog.arkency.com/2013/12/rails4-preloading/

  31. Queries N+1 Usuário seguindo outros usuários class User has_many :user_follows

    has_many :following, through: :user_follows end
  32. Uma query por usuário User.limit(10).each do |follower| # Eu estou

    seguindo esse usuário? if current_user.following.where(id: follower.id).exists? # imprime o botão "deixar de seguir" else # imprime o botão "seguir" end end A solução mais simples geralmente não é a mais performática.
  33. Salvando os ids de quem eu sigo # E se

    eu estiver seguindo 90mil usuários? following_ids = current_user.following_ids User.limit(10).each do |follower| if following_ids.include?(follower.id) # imprime o botão "deixar de seguir" else # imprime o botão "seguir" end end
  34. Salvando os ids de quem está sendo listado e eu

    sigo users = User.limit(10) user_ids = users.map(&:id) # Com INNER JOIN # following_ids = current_user.following.where(id: user_ids).pluck(:id) # Sem INNER JOIN following_ids = current_user.user_follows. where(following_id: user_ids).pluck(:following_id) users.each do |follower| if following_ids.include?(follower.id) # imprime o botão "deixar de seguir" else # imprime o botão "seguir" end end
  35. gem 'bullet' Chata! Alert, honeybadger, airbrake, slack, etc.

  36. gem 'rack-mini-profiler'

  37. gem 'newrelic_rpm' localhost:3000/newrelic

  38. Dashboard — data da primeira compra — data da última

    compra — número de compras — total gasto — categorias compradas — marcas compradas
  39. Informações triviais sozinhas class User def first_purchase_at orders.minimum(:sold_at) end def

    last_purchase_at orders.maximum(:sold_at) end def purchases_count orders.count end end
  40. Informações triviais sozinhas class User def purchases_total_price orders.sum(:price) end def

    purchased_brands purchased_products.pluck(:brand).uniq end def purchased_brands purchased_products.pluck(:category).uniq end end
  41. 6 consultas user.orders.minimum(:sold_at) user.orders.maximum(:sold_at) user.orders.count user.orders.sum(:price) user.purchased_products.pluck(:brand).uniq user.purchased_products.pluck(:category).uniq

  42. SQL + Ruby purchases = user.orders. order(sold_at: :desc). joins(:product).select( "orders.sold_at",

    "orders.price", "products.brand", "products.category" ).to_a
  43. SQL + Ruby aggregation purchases.sum(&:price) purchases.map(&:brand).uniq purchases.map(&:category).uniq purchases.first.sold_at purchases.last.sold_at purchases.count

  44. SQL aggregation purchases = user.orders.select( "max(orders.sold_at) AS last_purchased_at", "min(orders.sold_at) AS

    first_purchased_at", "sum(orders.price) AS total_price", "count(orders.id) AS total" ).to_a.first
  45. SQL aggregation # Query one purchases.total_price purchases.last_purchased_at purchases.first_purchased_at purchases.total #

    Query two user.purchased_products.pluck(:brand).uniq # Query three user.purchased_products.pluck(:category_id).uniq
  46. Opções 1 - todas as informacões em queries separadas 2

    - uma query com todos os dados separados e calculando com ruby 3 - uma query com os campos calculados e outras duas para o restante
  47. Benchmark 2 2 https://github.com/evanphx/benchmark-ips

  48. Calculating ------------------------------------- queries separadas 3.000 i/100ms cálculo SQL + queries

    8.000 i/100ms SQL + cálculo ruby 1.000 i/100ms ------------------------------------------------- queries separadas 46.789 (± 8.5%) i/s - 234.000 cálculo SQL + queries 81.732 (± 9.8%) i/s - 408.000 SQL + cálculo ruby 2.314 (± 0.0%) i/s - 12.000 Comparison: cálculo SQL + queries: 81.7 i/s queries separadas: 46.8 i/s - 1.75x slower SQL + cálculo ruby: 2.3 i/s - 35.32x slower
  49. Mais um exemplo photos.order('CASE WHEN selected = true THEN 0

    ELSE COALESCE(position, 0) + 1 END') VS photos.sort_by do |photo| if photo.selected? 0 else photo.position.to_i + 1 end end
  50. Calculating ------------------------------------- SQL 1.000 i/100ms Array 1.000 i/100ms ------------------------------------------------- SQL

    1.054k (± 8.1%) i/s - 5.074k Array 95.344 (± 9.4%) i/s - 468.000 Comparison: SQL: 1053.8 i/s Array: 95.3 i/s - 11.05x slower
  51. Mais sobre ordenação

  52. Ordenação na query + limit EXPLAIN SELECT * FROM messages

    WHERE user_id = 1 ORDER BY id DESC LIMIT 10 Limit (cost=539.60..539.63 rows=10 width=115) -> Sort (cost=539.60..539.94 rows=136 width=115) Sort Key: id -> Bitmap Heap Scan on messages (cost=5.49..536.67 rows=136 width=115) Recheck Cond: (user_id = 1) -> Bitmap Index Scan on index_messages_on_user_id (cost=0.00..5.45 rows=136 width=0) Index Cond: (user_id = 1)
  53. Usando índice com ordenação CREATE INDEX index_messages_ordered_on_user_id ON messages (user_id,

    id DESC) Limit (cost=0.43..40.78 rows=10 width=115) -> Index Scan using index_messages_ordered_on_user_id on messages (cost=0.43..549.14 rows=136 width=115) Index Cond: (user_id = 1)
  54. None
  55. Cache — Fragment — Russian doll — Rack — HTTP

    Speed Up Your Rails App by 66% - The Complete Guide to Rails Caching 3 3 From Nate Berkopec
  56. gem 'identity_cache' class Product < ActiveRecord::Base include IdentityCache has_many :photos

    belongs_to :category cache_has_many :photos cache_belongs_to :category end product = Product.fetch(1) product.fetch_images product.fetch_category
  57. class Photo < ActiveRecord::Base include IdentityCache cache_index :imageable_type, :imageable_id end

    class Product < ActiveRecord::Base include IdentityCache def cached_images Photo. fetch_by_imageable_type_and_imageable_id( 'Product', id ) end end
  58. Counter cache — Implementação do Rails class Order < ActiveRecord::Base

    belongs_to :customer, counter_cache: true end class Customer < ActiveRecord::Base has_many :orders end @customer.orders.size @customer.orders.count
  59. Counter cache gem 'counter_culture' class Product < ActiveRecord::Base belongs_to :sub_category

    counter_culture :category, :column_name => Proc.new {|model| model.special? ? 'special_count' : nil } counter_culture :category, :column_name => 'product_weight_ounces', :delta_column => 'weight_ounces' end @category.special_count @category.product_weight_ounces
  60. Paginação — remover a numeração das páginas — usar o

    counter cache para calcular a numeração current_user. followers. paginate(per_page: 10, page: params[:page], total_entries: current_user.followers_count)
  61. — Remover locks pessismistas desnecessários Comment.lock.find(1).destroy comment = Comment.find(1) comment.with_lock

    do comment.destroy! end
  62. Admin no servidor do usuário final — Admin é sempre

    mais lento — Relatórios — Cheio de ações demoradas Boa solução — Reverse proxy
  63. Lazy load na interface — Por que carregar os comentários

    na ação principal? — Por que carregar os produtos relacionados na ação principal? — ...
  64. Migration sem lock na tabela class AddIndexToMessages < ActiveRecord::Migration disable_ddl_transaction!

    def change add_index :messages, :user_id, algorithm: :concurrently end end
  65. None
  66. Fim.

  67. caju@enjoei.com.br

  68. Perguntas?

  69. Referência das imagens — https://en.wikipedia.org/wiki/Rider-Waitetarotdeck — http://www.maxi-geek.com/2015/05/the-flash-vs- superman-who-will-bleed.html — https://sobeso.com/beating-creative-block