The document provides tips for improving performance in a Ruby on Rails application. It discusses using replication and read-only databases for certain queries and background jobs to improve performance. It also provides examples of preloading association data, using aggregation in SQL queries instead of separate queries, adding indexes, and implementing caching to reduce the number of database queries.
2. Marcelo Cajueiro
• 5 anos trabalhando com Ruby on Rails
• Engenheiro e líder no Enjoei =P
• Não toco nenhum instrumento
• Nick no chat da uol: tatuado_25_na_cam !
• http://cajueiro.me
28. gem 'octopus'
class Badge < ActiveRecord::Base
replicated_model
end
Badge.where(user_id: 1) # usa réplica
@user = User.using(:shard1).find_by_name("Joao")
PS.: use com sabedoria.
39. Uma query por usuário
User.limit(10).each do |user|
# Eu estou seguindo esse usuário?
if current_user.following.where(id: user.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.
40. 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 |user|
if following_ids.include?(user.id)
# imprime o botão "deixar de seguir"
else
# imprime o botão "seguir"
end
end
42. users = User.limit(10)
user_ids = users.map(&:id) # pluck? no :)
following_ids = current_user.
following.where(id: user_ids).pluck(:id)
users.each do |user|
if following_ids.include?(user.id)
# imprime o botão "deixar de seguir"
else
# imprime o botão "seguir"
end
end
44. Com INNER JOIN
following_ids = current_user.
following.where(id: user_ids).pluck(:id)
SELECT "users"."id"
FROM "users"
INNER JOIN "user_follows"
ON "users"."id" = "user_follows"."user_id"
WHERE "user_follows"."follower_id" = 1
45. Sem INNER JOIN
following_ids = UserFollow.
where(follower_id: current_user.id).
pluck(:user_id)
SELECT "user_follows"."user_id"
FROM "user_follows"
WHERE "user_follows"."follower_id" = 1
Nem sempre o que o Rails tem pronto é o melhor.
50. Dashboard
• data da primeira compra
• data da última compra
• número de compras
• total gasto
• categorias compradas
• marcas compradas
51. 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
52. 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
56. 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
58. 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
61. Mais um exemplo
photos.order('CASE WHEN selected = true
THEN 0
ELSE COALESCE(position, 0) + 1 END')
Ou
photos.sort_by do |photo|
if photo.selected?
0
else
photo.position.to_i + 1
end
end
64. 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)
65. 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)
66.
67. Cache
• Fragment
• Russian doll
• Rack
• HTTP
Speed Up Your Rails App by 66% - The Complete Guide to
Rails Caching 4
4
From Nate Berkopec
68. 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_photos
product.fetch_category
69. class Photo < ActiveRecord::Base
include IdentityCache
cache_index :imageable_type, :imageable_id
end
class Product < ActiveRecord::Base
def cached_images
Photo.
fetch_by_imageable_type_and_imageable_id(
'Product', id
)
end
end
70. 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
72. Paginação
• usar o counter cache para calcular a numeração
• remover a numeração das páginas
current_user.
followers.
paginate(per_page: 10,
page: params[:page],
total_entries: current_user.followers_count)
73. Remover locks pessismistas desnecessários
Comment.lock.find(1).destroy
comment = Comment.find(1)
comment.with_lock do
comment.destroy!
end
74. Admin no servidor do usuário final
• Admin é sempre mais lento
• Tem Relatórios
• Cheio de ações demoradas
Boa solução
• Proxy reverso
75. Lazy load na interface
• Por que carregar os comentários na ação principal?
• Por que carregar os produtos relacionados na ação
principal?
• ...
76. Migration sem lock na tabela
class AddIndexToMessages < ActiveRecord::Migration
disable_ddl_transaction!
def change
add_index :messages, :user_id, algorithm: :concurrently
end
end