Maior performance no seu sistema
com o uso adequado de
ORM em Rails
Isaac Felisberto de Souza
Engenheiro de Software
...há “quase” 20 anos
no mundo de desenvolvimento
Já somos mais de 100 na área
de desenvolvimento!
Vamos usar outra linguagem
Ruby é lento
Vamos fragmentar o sistema
Precisamos reescrever do zero
Será que o problema é a
linguagem ou o framework?
Ou não estamos usando
eles de forma adequada?
ORM
EM
RAILS
Object
Relational
Mapping
Rails nos oferece
Active
Record
Ele é um Pattern!
Martin Fowler
Order
Customer
Order Items
Product
Shipping Company
Delivery
>Order.create(customer:...)
>Order.find(1).customer.name
>Order.find_by(customer:1).order_items.count
>Order.all
class Customer < ApplicationRecord
end
class Delivery < ApplicationRecord
belongs_to :shipping_company
belongs_to :ordem_item
end
class Order < ApplicationRecord
belongs_to :customer
has_many :order_items
end
class OrderItem < ApplicationRecord
belongs_to :order
belongs_to :product
has_one :delivery
end
class Product < ApplicationRecord
end
class ShippingCompany < ApplicationRecord
end
Order Load (1.0ms) SELECT "orders".* FROM
"orders" LIMIT $1 [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Order id: 1,
customer_id: 2, status: "ENTREGUE", ...]>
<table>
<thead>
<tr>
<th>Id</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<% @orders.each do |order| %>
<tr>
<td><%= order.id %></td>
<td><%= order.status %></td>
</tr>
<% end %>
</tbody>
</table>
<table>
<thead>
<tr>
<th>Customer</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<% @orders.each do |order| %>
<tr>
<td><%= order.customer.name %></td>
<td><%= order.status %></td>
</tr>
<% end %>
</tbody>
</table>
Para 10.000 orders, até 10.001 consultas ao banco
(Queries N+1)
def index
@orders = Order.all
end
def index
@orders = Order.all.includes(:customer)
end
2 consultas ao banco de dados
def index
@orders = Order.all.joins(:customer).includes(:customer)
end
1 consulta ao banco de dados
Default
16171ms
9408ms
9574ms
9783ms
17361ms
19387ms
13.6s
Qual melhor estratégia?
Include
1490ms
1440ms
1844ms
1581ms
1555ms
1969ms
1.6s
Join + Include
2044ms
2168ms
1536ms
1903ms
1978ms
1668ms
1.8s
+ db index
1579ms
1653ms
1575ms
1556ms
1613ms
1564ms
1.5s
Como coletar
essas métricas?
Uso do log do rails server
Application
Performance
Management (APM)
15ms
1ms
https://github.com/newrelic/rpm
Porque esses
problemas ocorrem ?
SOLUÇÃO CRESCE !
● FEATURES COMPLEXAS
O TIME CRESCE !!!
● MAPEAMENTOS EQUIVOCADOS
● MAU USO DE NAVEGAÇÃO
ENTRE OBJETOS
● MODELAGEM CONFUSA
Como evitar
esses problemas ?
Bullet
https://github.com/flyerhzm/bullet
DB Query Matchers
https://github.com/brigade/db-query-matchers
teste A
teste B
teste C
teste D
teste E
teste F
teste G
teste H
teste I
teste J
teste K
teste L
require 'rails_helper'
feature 'orders index' do
scenario 'list all' do
expect { visit('/orders') }.to make_database_queries(count: 3)
end
scenario 'list all' do
expect { visit('/orders_include') }.to make_database_queries(count: 2)
end
scenario 'list all' do
expect { visit('/orders_join') }.to make_database_queries(count: 1)
end
end
expect { }.to_not make_database_queries(matching: /UPDATE/)
expect { }.to_not make_database_queries
expect { }.to make_database_queries(count: 1)
expect { }.to make_database_queries(count: 0..3)
expect { }.to_not make_database_queries(manipulative: true)
(INSERT, UPDATE, DELETE)
Boas
Práticas
Uso de Scope
def index_with_join
@orders = Order.all.joins(:customer).includes(:customer)
end
Controller
class Order < ApplicationRecord
belongs_to :customer
has_many :order_items
scope :all_with_customer, -> {Order.all.joins(:customer).includes(:customer)}
end
Model
def index_with_join
@orders = Order.all_with_customer
end
Controller
Evite...
scope :all_with_customer, -> {
Order.all.includes(:customer).order(:status)}
Ordenação
default_scope :active, -> {Customer.where(active:true)}Default
scope :closed, -> {Order.where(status:‘closed’)
.where(‘total_amount >= ?’, 10.0)}
Itens
Implícitos
scope :closed, -> do
find_by_sql <<-SQL
select * from orders where status = ‘closed’
SQL
end
SQL puro
Queries customizadas com
Mapeamento Alternativos
class SummaryOrder < ApplicationRecord
self.table_name = "orders"
scope :all_summarized, -> {
select('orders.id')
.select('customers.name')
.select('orders.status')
.select('count(order_items.id) as items_quantity')
.joins('left join customers on orders.customer_id = customers.id')
.joins('right join order_items on order_items.order_id = orders.id')
.group('orders.id, customers.name, orders.status')
}
end
...
<tr>
<th>Customer</th>
<th>Status</th>
<th>Quantity of Items</th>
</tr>
...
<% @summary_orders.each do |summary_order| %>
<tr>
<td><%= summary_order.name %></td>
<td><%= summary_order.status %></td>
<td><%= summary_order.items_quantity %></td>
</tr>
<% end %>
...
class SummaryOrder
attr_accessor :id,
:name,
:status,
:items_quantity
end
count, length ou size ?
count
length
size
OO ou SQL?
Dê preferência ao uso de OO!
OO não pode sacrificar performance
ORM é um facilitador, conhecer SQL e banco de dados
ainda é importante
Não abra mão do poder do banco de dados
> SummaryOrder.all_summarized.explain
HashAggregate (cost=871.09..971.09 rows=10000 width=57)
Group Key: orders.id, customers.name
-> Hash Left Join (cost=349.70..796.09 rows=10000 width=57)
Hash Cond: (orders.customer_id = customers.id)
-> Hash Left Join (cost=319.00..639.24 rows=10000 width=33)
Hash Cond: (order_items.order_id = orders.id)
-> Seq Scan on order_items (cost=0.00..194.00 rows=10000 width=16)
-> Hash (cost=194.00..194.00 rows=10000 width=25)
-> Seq Scan on orders (cost=0.00..194.00 rows=10000 width=25)
-> Hash (cost=19.20..19.20 rows=920 width=40)
-> Seq Scan on customers (cost=0.00..19.20 rows=920 width=40)
Explain
Vimos resumidamente...
Includes
Joins Scopes
Mapeamentos Alternativos
New Relic Bullet
DB Query Matchers
Queries N+1
Count, Length, Size
Explain
Isso resolve todos
os problemas ?
provavelmente
não!
Talvez ainda seja
necessário...
● Reescrever
● Fragmentar
● Ou até usar outra
linguagem
Faça pelos
motivos adequados!
Verifique se o
problema de performance
não é mal uso de ORM
Obrigado ! Isaac Felisberto de Souza
isaacsouza@gmail.com
linkedin.com/in/isaacfsouza
http://resultadosdigitais.com.br/
trabalhe-conosco/
Seja um RDoer!
Dúvidas ?

TDC2018SP | Trilha Ruby - Maior performance no seu sistema com o uso adequado de ORM em Rails