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

Performance - Vamos dormir mais @ 1º Tech Day G...

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.

Marcelo Cajueiro

August 10, 2015
Tweet

More Decks by Marcelo Cajueiro

Other Decks in Programming

Transcript

  1. 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
  2. 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.
  3. 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
  4. 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
  5. Dashboard — data da primeira compra — data da última

    compra — número de compras — total gasto — categorias compradas — marcas compradas
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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)
  15. 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)
  16. Cache — Fragment — Russian doll — Rack — HTTP

    Speed Up Your Rails App by 66% - The Complete Guide to Rails Caching 3 3 From Nate Berkopec
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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)
  22. Admin no servidor do usuário final — Admin é sempre

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

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

    def change add_index :messages, :user_id, algorithm: :concurrently end end