¿rails no escala?
Post on 11-Jul-2015
71 Views
Preview:
TRANSCRIPT
¿Rails no escala?
A veces no es suficiente que funcione...
Rankia
- 2M+ contenidos
- 100K+ usuarios registrados
- 10M páginas/mes
- 1500 páginas/minuto en las horas punta
Si algo funciona, pero funciona mal,
posiblemente el servidor se vendrá abajo...
Escalabilidad no es sobreoptimizar
- La velocidad es muy importante para la web
- Pero rascar unas milésimas no justifica sacrificar
la legibilidad o mantenibilidad del código
- Escalabilidad no es bajar de 100 a 80, sino bajar
de 100 a 1 -> eliminar problemas
Sobreoptimizar
Usuario.select(:nick, :email, :estado)
- Ganancia despreciable
- Se está comprometiendo la compatibilidad con métodos
(presentes o futuros) que se apoyen en otros campos
- Si alguna validación (presente o futura) mira otros
campos, p.ej. código postal, no se podrán guardar
cambios.
Pero las reglas están para romperlas
http://www.rankia.com/blog/blogs-en-rankia/ultimo
- Página con mucho tráfico -> las ganancias que aquí
consiga se multiplican
- Los posts son contenidos con textos muy largos que
aquí no voy a mostrar -> la ganancia es menos
despreciable
Las reglas hay que conocerlas, y saber por qué se han
puesto, para saber cuándo romperlas… como en todo.
Optimización del backend
- Update_all
- n+1 consultas
- No instanciar para contar
- Índices
- Desnormalización
- Cachés
- Configuración de servidores
Update_all
Hay que guardar la URL de los posts de blog
en la BD; son 200 blogs y 30.000 postsBlogs.all.each do |blog|
blog.posts.each do |post|
url = ”/blog/#{ blog.url }/#{ post.id }-#{ post.permalink }”
post.update_attribute(:url, url)
end
end
Update_all
Behind the scenes…
- 1 lectura a blogs (200 registros)
- 200 lecturas a posts (~150 registros c/u)
- 30.000 escrituras a posts
Houston, tenemos un problema...
Update_all
Post.rb
validates :titulo, presence: true, length: { maximum: 120 }
validates :descripcion, length: { maximum: 500 }, allow_blank: true
use_permalink(:titulo)
before_save :limpia_tag_list
after_create :limpia_titulo
after_update :actualiza_cache_actividad
after_update :carga_fecha_titulares_en_tags
Y pensabais que las 30.000 escrituras eran el problema...
Update_all
Blogs.all.each do |blog|
url = ”CONCAT(‘/blog/#{blog.url}/’, id, ‘-’, permalink)”
Post.where(blog_id: blog.id).update_all(:url, url)
end
- 1 lectura a blogs (200 registros)
- 200 escrituras a posts (~150 registros c/u)
- 0 validaciones y hooks
n+1 consultas
http://www.rankia.com/foros/bolsa/temas@foro.temas.order(ultima_respuesta: :desc).each do |tema|
link_to tema
link_to tema.autor
link_to tema.ultima_respuesta
link_to tema.ultima_respuesta.autor
Consultas: 1 temas, 36 últimas respuestas, 72 autores -> total 109
n+1 consultas
http://www.rankia.com/foros/bolsa/temas@foro.temas.order(ultima_respuesta: :desc)
.includes(:autor, ultima_respuesta: :autor)
.each do |tema|
Consultas: 1 temas, 1 últimas respuestas, 2 autores -> total 4
No instanciar para contar
Es importante conocer la diferencia entre .count y .length
Y ante la duda, el comodín .size
Índices
http://www.rankia.com/foros/bolsa/temas/510770-pulso-mercado
~90.000 respuestas, ~11.000 páginas
Tabla contenidos (+2M registros)
Índice: tema + publicado + created_at
Consulta: tema.respuestas.order(:created_at) -> FAIL
Necesita un índice por tema + created at
(primero criterio de filtrado y luego el de ordenación)
Antes
Gráfico de skylight:
Después
Pero el problema no ha acabado...
http://www.rankia.com/foros/bolsa/temas/510770-pulso-mercado?page=11156
Gigantesco offset -> Obligo a la BD a recorrer todas las respuestas… OMG!!
Solución (ahora sí)
- Guardar el nº de página en la tabla contenidos
- Índice por tema + página (+ created_at)tema.respuestas
.where(pagina: params[:pagina])
.order(created_at: :desc)
-> Accesos instantáneos!
...pero acabas de romper el will_paginate :-(
La escalabilidad muchas veces necesita trajes a medida
Desnormalización
Problema: el filtrado/ordenación afecta a otras tablas
-> No podemos usar índices
Ej: Lista depósitos ordenados por nombre del banco
Deposito.includes(:banco).order(‘banco.nombre’)
Desnormalización
Solución: Copiar el nombre del banco en el depósito y
crear un índice por ese campo.
Deposito.order(:nombre_banco)
Solución razonablemente buena si el dato desnormalizado
no va a cambiar; si se permite cambiar el nombre del
banco, al cambiarlo habrá que actualizar en cascada:
banco.depositos.update_all(nombre_banco: banco.nombre)
Desnormalización
- Es un “mal necesario” en ocasiones
- NoSQL resuelve mejor estos problemas
- Buscar alternativas antes de desnormalizar
Cachés
- Deben usarse para hacer que vaya aún mejor, no para
“barrer bajo la alfombra” un problema
- Incluir el “updated_at” en la clave de la caché (Rails lo
hace por defecto) es la mejor forma de expirarlas.belongs_to :producto, touch: true
cache(@producto) do …
Cachés
Rankia con y sin caché (fallo de Memcache en un takeover):
Configuración de servidores
Optimización del frontend
- La escalabilidad es un problema de backend
- Pero la velocidad es un problema de front:
Optimización del frontend
- Imágenes comprimidas
- Reducir peticiones
- CSS sin excesivo anidamiento
- Páginas ligeras
- CDN
- ¡Medir!
Imágenes comprimidas
- ImageOptim / PngGauntlet
- Nunca usar imágenes grandes reducidas por
CSS -> Ojo con Responsive Design!
- En su lugar: media queries, HTML5 picture
Imágenes comprimidas
Show me the code:
http://www.html5rocks.com/en/tutorials/responsive/picture-element/
Reducir peticiones
- Javascript/CSS: Asset Pipeline se encarga. Uno de
cada, o quizá dos para cachear agresivamente lo
menos cambiante.
- Imágenes: Sprites, imágenes embebidas<img src="data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge8W
SLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKc
ppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/g
AwXEQA7" width="16" height="14" alt="embedded folder icon">
- Banners, DNS, …
Reducir peticiones
- La petición más rápida es la que no se hace porque el
archivo ya está cacheado en el cliente: Indicar
expiraciones... o dejar que el Asset Pipeline lo haga por ti
- Ojo con los banners, son la muerte necesaria, pero al
menos cargarlos al final
CSS sin excesivo anidamiento
- El CSS no necesita emular la estructura del HTML.columna
.bloque_lateral
.lista_enlaces
a.link_usuario
color: red
NO. Con nombres no demasiado genéricos, dos niveles
deberían ser suficientes, máximo tres.
Paginas ligeras
- Limpia el HTML
- El diseñador debe implicarse: menos es más
- Ojo con el Responsive Design y cargar
elementos para ocultarlos por CSS
CDN
- Se encarga por ti de varias de las técnicas
de front descritas, y alguna más:
prefetching.
- Akamai es la caña… pero caro.
- Google Pagespeed, en beta, es gratis
¡Medir!
- YSlow
- WebPageTest.org
- WooRank
- NewRelic, Skylight...
Bonus tip
Siempre, a la hora de optimizar, buscar el cuello de botella:
lo más lento, lo más frecuentado... optimizar donde no está
el problema tiene un retorno bajo.
top related