Este documento narra la historia de cómo un sistema experimentaba problemas de rendimiento debido a una alta carga. El autor implementó varias soluciones iterativas como agregar más contenedores, dividir el tráfico entre backends y optimizar consultas a bases de datos. Estas mejoras redujeron las latencias del API de varios segundos a solo unos milisegundos. El autor concluye que es importante medir el rendimiento, identificar cuellos de botella y hacer cambios graduales para resolver problemas de escalabilidad.
1. Mis primeros pasos usando las
escaleras
Alejandro BM -@ae_bm
Mad Scalability 2018/4/4
2. Trigger Warning
● Puedo herir sensibilidades. Se recomienda discreción
● No hablare de nada esotérico o exótico
– No cloud
– No scheduler de containers
– No SDNs
– Etc
● Si esto parece relleno ^_^ don’t blame me
● Esta presentación no ha sido revisada por Relaciones
Públicas
● La siguiente es una historia inspirada en hechos reales
3. En un comienzo
● En un hardware que tiene varios sistemas
ejecutándose (Gente a la que le gusta ahorrar)
● Hay quejas sobre la lentitud del sistema
● Los recursos están saturados
● El contenedor con el proceso web está a tope
de CPU
● Se cambia la máquina por una más potente
● Aún con más recursos el sistema funciona de
pena ¯_(ツ)_/¯
5. En un comienzo
● Comenté que un sólo contenedor con un proceso
python no iba a utilizar más cores por arte de
magia
● Se decide poner otro contenedor con un proceso
python para aprovechar mejor los recursos de la
máquina. Ahora NGINX además de proxy funciona
como load balancer
● El sistema se pensó para no usar cookies o un
almacén externo de sesiones, lo cual maravilla a
los usuarios cuando peticiones relacionadas son
procesadas por backends distintos
6.
7. En un comienzo
● Mientras tanto yo analizaba logs
● Vi que la mayoría de las peticiones venían de
la propia máquina originadas por un sistema
interno
● Así que configuré a NGINX para que las
peticiones del sistema interno usaran un
contenedor y el resto el otro contenedor
● Mi idea era que los usuarios externos no fueran
penalizados por el sistema interno
9. Unas semanas después
● Quejas porque el sistema está lento
● El backend para peticiones internas está casi
ocioso mientras su clon está comiéndose casi
toda la carga
● Esta vez pude revisar de nuevo los logs sin que
se tomaran decisiones apresuradas
● Omití todo el trafico originado desde localhost
10. Unas semanas después
● Existían 2 tipos de peticiones desde clientes
externos
– URLs con /html/
– URLs sin /html/
● Después de una comunicación OPS DEV
– Si la URL tiene /html/ se han hecho con un
navegador web – Posible humano
– Si no tienen /html/ son consultas al API – Posible
no humano
11. Unas semanas después
● Manteniendo la doctrina de no penalizar humanos*, se configura el
sistema para usar otro backend más
– 1 backend para peticiones desde localhost (siguen siendo un montón)
– 1 backend para peticiones sin /html/ (peticiones al API)
– 1 backend para peticiones con /html/ (peticiones de humanos)
● Cambio el formato de logs en NGINX para tener más datos
– Saber que backend procesó la petición $proxy_host
– Saber el tiempo que tardo la petición $request_time
* still a misanthrope ^_^
13. Hace unas pocas semanas
● El cliente comenta que el sistema esta lento
● La página web funciona con las latencias
esperadas
● El cliente especifica que el problema es con el
API – Ahora son específicos
● La ventaja es que ahora hay datos y no sólo
sensaciones
14.
15. Hace unas pocas semanas
● Aplicando estadísticas a los logs de un mes y
medio además de comparándolo con otro
cliente grande
16. Hace unas pocas semanas
● Peticiones procesadas por cada backend
● Respuestas HTTP retornadas por NGINX
local 0.98
html 0.01
api 0.0043
200 0.9989
404 0.0008
17. Hace unas pocas semanas
Histograma de la duración de las peticiones
18. Hace unas pocas semanas
Duración de las peticiones en base al tiempo
19. Hace unas pocas semanas
Histograma de las duraciones menores a 1 seg
20. Hace unas pocas semanas
● Estadísticas
● Nada llamativo
min 0.0 q1 0.003
max 63.287 q3 0.005
mean 0.04 IQR 0.002
median 0.004 p90 0.008
mad 0.07 p99 0.6
stdev 0.67
21. Hace unas pocas semanas
● Dos errores
– Revisar casi 2 meses de logs
– Revisar el agregado y no el API
● Viendo sólo los números parece que el sistema
no va tan mal (considerando que nadie ha
definido una latencia objetivo)
● El problema es la gran cantidad de peticiones
que hace el sistema local. Por lo que el resto
de peticiones son outliers
22.
23. Hace unas pocas semanas
API - Histograma de la duración de las peticiones
24. Hace unas pocas semanas
API - Duración de las peticiones en base al tiempo
25. Hace unas pocas semanas
● Estadísticas - API
● FUUUUUUU…..
min 0.0 q1 4.76975
max 36.394 q3 7.9235
mean 5.97 IQR 3.15375
median 5.212 p90 11.4481
mad 3.41 p99 25.12393
stdev 4.98
26.
27. Hace unas pocas semanas
● El backend que atiende las peticiones del API
sucks hard
● Me intrigaba que un proceso web usara
constantemente 100% una CPU
● No es una aplicación que mina criptomonedas
● Confirmo que es una aplicación asíncrona
28. Hace unas pocas semanas
● Decido formular una hipótesis:
– Estamos aceptando peticiones que no avanzan
porque están esperando por otras partes del
sistema. Es el proceso de aceptar peticiones de
forma continua lo que hace que se use el CPU al
100%
● Toca ver si existen recursos críticos
30. Hace unas pocas semanas
● Para validar la hipótesis decido usar sysdig
para ver cuales son las llamadas al sistema
que más tardan en responder
● Todo está lleno de epolls, así que descarto los
epolls – normal en un sistema asíncrono
● Cuando ignoro los epolls:
– La mayoría de las esperas son por MySQL
(ToySQL)
– Hay mucha comunicación de red con REDIS
31. Hace unas pocas semanas
La parte del sistema que nos interesa ahora
32. Hace unas pocas semanas
● Con MySQL ocurrían dos problemas:
– Una query que hacia un order by con limit en un
campo que no tenia índice – facepalm
– Una query de union que tenia índices pero el filtrado
se hacia en el having por lo que no usaba índices –
sin comentarios
● Soluciones:
– Crear un índice para la primera consulta
– Reescribir la query para que el filtrado se haga en los
where y no esperar hasta el final (el having) – Ahora
me llaman DBA por esto. FML
33. Hace unas pocas semanas
● Ahora las queries a la BD iban algo mejor -
mucho
● Pero persistía el alto uso de CPU
● Con sysdig veo que peticiones del tipo /algo?
user=id
– Se conectan a REDIS y piden una colección de
miles de elementos
– Reviso el código fuente y el filtrado lo está
haciendo python - LLORO
34.
35. Hace unas pocas semanas
● Soluciones:
– Usar otra BD – No hay tiempo
– Se utiliza otra estructura en REDIS para soportar
este tipo de consultas
● Se dejo unos días para ver si había mejora y ...
36. Hace unas pocas semanas
API - Histograma de la duración de las peticiones
37. Hace unas pocas semanas
API - Duración de las peticiones en base al tiempo
38. Hace unas pocas semanas
● Estadísticas – API
● Parece que hemos mejorado un poco el
sistema
min 0.002 q1 0.004
max 3.037 q3 0.006
mean 0.03 IQR 0.002
median 0.005 p90 0.01
mad 0.04 p99 0.70078
stdev 0.13
39.
40. Como conclusión
● Definir la latencia aceptable
● Medir / instrumentar
● Entender lo que se esta midiendo
● Hacer experimentos
● Hacer pruebas de carga de verdad
41. Como conclusión
● Ir de forma iterativa corrigiendo los bottlenecks
● Vale la pena diferenciar los tipos de carga de
trabajo
● Usar loadsheding, quotas o fallar es mejor a
crear un fallo en cascada
https://landing.google.com/sre/book/chapters/addressing-cascading-failures.html
42. Como conclusión
● Antes de escalar en máquinas o cambiar el HW
entender lo que esta limitando la escalabilidad
● Revisar los queries con explain y usar el f***
index
● Si la BD te puede dar el trabajo ya hecho es
mucho mejor hacerlo en la aplicación:
– Menos tráfico de red
– No malgastas CPU
43. ¿Preguntas?
se supone que es una lighting talk
Things That Are Not Questions (by @QuinnyPig)
• Calling Bullshit
• Telling a pointless story
• A spoken word version of your resume