2. Introducción
optimizar != escalar
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
3. Introducción
“La optimización es el proceso de
búsqueda de la mejor manera de realizar un
proceso, con respecto a uno o más
recursos, que pueden ser: tiempo de
ejecución, uso de memoria, uso de CPU...”
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
4. Introducción
“La escalabilidad es la propiedad deseable
de un sistema, que indica su habilidad para,
o bien manejar el crecimiento continuo de
trabajo de manera fluida, o bien para estar
preparado para hacerse más grande con un
impacto mínimo en el redimiento.”
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
5. Introducción
“El rendimiento es la relación entre los
resultados obtenidos y los recursos
utilizados.”
e = resultados / recursos
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
6. Introducción
Optimización => Aumentar el rendimiento
Escalabilidad => Mantener el rendimiento
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
7. Reducir el tamaño de las consultas
Consulta ineficiente:
def index
@users = User.find(:all, :limit => 20)
end
SELECT * FROM users LIMIT 0,10 ORDER BY id desc
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
8. Reducir el tamaño de las consultas
Optimización:
def index
@users = User.find(:all, :select => “id, name”, :limit => 20)
end
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
9. Reducir el tamaño de las consultas
Benchmark (75000 registros):
#1 User.find(:all, :limit => 20)
#2 User.find(:all, :limit => 20, :select => “id, name, surname”)
user system total real
#1 0.000000 0.000000 0.000000 ( 0.000927)
#2 0.000000 0.000000 0.000000 ( 0.000552)
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
10. Reducir el tamaño de las consultas
Benchmark (75000 registros):
#1 User.find(:all)
#2 User.find(:all, :select => “id, name, surname”)
user system total real
#1 6.520000 0.350000 6.870000 ( 7.945950)
#2 2.140000 0.040000 2.180000 ( 2.931969)
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
11. Contadores
Categorias
- Ruby (1345)
- Rails (2389)
- Testing (345)
- Performance (34)
for cat in @categories
puts “#{cat.name} (#{cat.posts.count})”
end
=> select count(*) from posts where category_id = id
500.000 posts x 4 categorias = 2.000.000 de registros consultados!!
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
12. Contadores
Optimización:
class Post < ActiveRecord::Base
belongs_to :category, :counter_cache => true
end
create_table :categories do |t|
t.string :name
...
t.integer :posts_count, :default => 0
t.timestamps
end
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
13. Contadores
for cat in @categories
puts “#{cat.name} (#{cat.posts_count})”
end
Evitamos los 2.000.000 de registros recorridos en 4 consultas
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
14. Eager Loading
Consulta ineficiente:
def index
@posts = Post.find(:all, :limit => 10)
end
for post in @posts
puts “Titulo:” + post.title
puts “Autor:” + post.user.name
end
=> 1+10 querys
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
15. Eager Loading
Optimización:
def index
@posts = Post.find(:all, :limit => 10, :include => :user)
end
for post in @posts
puts “Titulo:” + post.title
puts “Autor:” + post.user.name
end
=> 1+1 querys
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
16. Eager Loading
Optimización:
def index
@posts = Post.find(:all, :limit => 10, :select => “posts.id,
posts.title”, :include => :user)
end
for post in @posts
puts “Titulo:” + post.title
puts “Autor:” + post.user.name
end
=> 1+1 querys
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
17. Eager Loading
Problema:
Podríamos hacer...
def index
@posts = Post.find(:all, :limit => 10, :select => “posts.id,
posts.title, user.id, user.name”, :include => :user)
end
Pero no funciona :(
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
18. Eager Loading
Optimización:
def index
@posts = Post.find(:all, :limit => 10, :select => quot;posts.title,
users.name AS user_namequot;, :joins => [:user])
end
for post in @posts
puts “Titulo:” + post.title
puts “Autor:” + post.user_name
end
=> 1 query
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
19. Eager Loading
Consulta ineficiente:
def index
@post = Post.find(params[:id])
end
for comment in @post.comments
puts comment.body
puts “Autor:” + comment.user.name
end
=> 1 query para el post
=> 1 query para los comentarios
=> N querys para los usuarios
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
20. Eager Loading
Optimización:
def index
@post = Post.find(params[:id])
@comments = @post.comments.find(:all, :include => :user)
end
for comment in @comments
puts comment.body
puts “Autor:” + comment.user.name
end
=> 1 query para el post
=> 1 query para los comentarios
=> 1 query para los usuarios
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
21. Eager Loading
Optimización:
def index
@post = Post.find(params[:id],
:include => [:user, :comments])
end
puts “Post:” + @post.body
puts “Autor:” + @post.user.name
for comment in @post.comments
puts “Comentario:” + comment.body
end
=> 3 querys
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
22. Tareas en background
Si con lo anterior no es suficiente para optimizar una consulta
siempre nos queda el Método de toda la vida:
User.find_by_sql(SELECT id, name, surname WHERE user.name = 'Pepe')
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
23. Tareas en background
Sacar tareas fuera del ciclo del request
- Envío de emails
- Cálculos
- Tareas de mantenimiento
...
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
24. Tareas en background
Opciones:
- script/runner
- daemon_generator
- BackgrounDRB
- Spawn
- Starling
...
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
25. Índices
Consulta ineficiente:
@users = User.find(:all, :conditions => “name = Pepe”)
Recorre toda la tabla
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
26. Índices
Evitar consultas que recorran toda la tabla (*)
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
27. Índices
Restricciones:
- No utilizar índices en columnas que se actualizan
frecuentemente
- No utilizar índices en columnas con poca variación (p.e.
booleanos)
- No utilizar Índices en tablas pequeñas
- No utilizar índices muy grandes
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
28. Índices
Optimización
Añadimos un índice en el campo “name” con una migración
add_index :users, :name
Benchmark 75000 usuarios
@users = User.find(:all, :conditions => “name = Pepe”)
user system total real
#1 0.010000 0.000000 0.010000 ( 0.631635)
#2 0.010000 0.000000 0.010000 ( 0.015232)
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
29. Índices
Podemos añadir índices multicolumna
add_index :users, [:name, :city]
No permite índices de más de 1024 bytes
En UTF-8 cada carácter necesita 3 bytes
(256+256)*3 = 1536 bytes
1536 x 500000 = 768 MB
Limitar el tamaño de los campos
t.column :login, :string, :limit => 10, :null => false
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
30. Índices
Definir la longitud del índice
def self.up
execute quot;CREATE INDEX full_name ON users (name(10), surname(10))quot;
end
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
31. Índices
“La regla de la izquierda”
Si se utilizan índices multicolumna en las cláusulas WHERE, hay
que incluir siempre de izquierda a derecha las columnas
indexadas
add_index :users, [:name, :surname, :city]
SELECT * from users WHERE city = ʻMadridʼ
SELECT * from users WHERE name = ʻPepeʼ
SELECT * from users WHERE name = ʻPepeʼ AND surname = ʻLopezʼ
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
32. Índices y ordenación
Podemos añadir índices multicolumna para ordenar
add_index :users, [:city, :created_at]
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
33. MyISAM vs InnoDB
Benchmark 75000 usuarios
User.count
user system total real
#1 0.000000 0.000000 0.000000 ( 0.663447) (InnoDB)
#2 0.010000 0.000000 0.010000 ( 0.000688) (MyISAM)
User.find(:all)
user system total real
#1 11.040000 0.660000 11.700000 ( 12.585430) (InnoDB)
#2 11.070000 0.670000 11.740000 ( 12.124938) (MyISAM)
User.find(:all, :conditions => “name = ‘Pepe’”)
user system total real
#1 0.010000 0.000000 0.010000 ( 0.015615) (InnoDB)
#2 0.000000 0.000000 0.000000 ( 0.018247) (MyISAM)
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
34. MyISAM vs InnoDB
Benchmark 270.000 posts
Post.find_by_category_id(14)
#1 0.000000 0.000000 0.000000 ( 0.000865) (InnoDB)
#2 0.000000 0.000000 0.000000 ( 0.001019) (MyISAM)
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
35. MyISAM vs InnoDB
Benchmark 270.000 posts
Post.find(:all, :conditions => 'body LIKE “Hello”’)
user system total real
#1 0.000000 0.000000 0.000000 ( 3.471458) (InnoDB)
#2 0.000000 0.000000 0.000000 ( 3.371270) (MyISAM)
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
36. Herramientas
Antes de empezar a optimizar: rellenar la base de datos
- populator
- db-populate
- babel
...
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com
45. Resumen
• Optimizar es un proceso necesario y contínuo
• Optimizar a medida que desarrollamos o cuando
refactorizamos
• Utilizar herramientas para encontrar “slow
querys”
Optimización, rendimiento y escalabilidad en AR Emili Parreño - www.eparreno.com