Etiquetas

, , ,

El miércoles pasado di una charla de cómo tenemos Menéame en Amazon AWS. Iba a explicar, al final de la charla, un truco de “particionado” [ver nota al final] económico, pero que no pudo ser: me tocó vivir en directo una saturación de la base de datos, producida por el nombramiento del nuevo Papa. Ahora explico cuál es ese “truco” de “particionado” económico y sencillo que sirve para ahorrar costes en RDS, y luego qué pasó y cómo solucioné la saturación de una de las bases de datos (de allí la frase “cachea todo ¡estúpido!”, el estúpido soy yo😉 ).

La base de datos principal de Menéame está en MySQL sobre Amazon RDS, con Multi AZ, lo que significa que tenemos failback y failover over automático y desantendido si el master falla. Da mucha comodidad, pero también tiene su coste: se paga el doble (el tamaño que tenemos es el large).

Screenshot from 2013-03-15 20:51:07

Llevábamos varias semanas que a a veces superábamos el 90% de uso de CPU del master, por consumo exclusivo de las consultas web. La solución sencilla pero cara es “escalar verticalmente” la base de datos, pagaríamos el doble. La otra es tener un slave del mismo tamaño, y pagar un 50% más, pero aunque el software lo puede hacer, no me gustaba por la sobrecarga y verificación para enviar las modificaciones (y consultas posteriores del mismo script) sólo al master.

Pero hay otras posibilidades, como el particionado funcional. Es decir, distribuir los datos según las funciones que lo usan. El ejemplo de manual es poner por ejemplo los artículos en un servidor, y los comentarios en otros. Pero tampoco me llega a convencer esta idea. Hay otra que es mucho más sencilla y no requiere particionar la base de datos, sino hacer que algunos scripts consulten a un servidor slave, diferente. Esto es bastante habitual para consultas “pesadas”, u offline.

Ya la usábamos con las llamadas al API para consultar el estado de las noticias, ese botón de Menéame con el número de votos que se ven en algunos sitios. El software permite especificar un servidor diferente por script, pero sólo lo usábamos para ese, para que consulte a un slave de tamaño pequeño, y usado también para otras operaciones, como generar los índices para el buscador con Sphinx.

Era raro que tuviésemos tanto consumo, las consultas más frecuentes ya estaban muy optimizadas, por lo que analicé qué scripts producían mayor consumo. Me llevé una sorpresa. Uno de ellos era el de la notificación de los mensajes y respuestas a comentarios y notas que se muestra en la parte superior (cuando se está autentificado, y que llama cada pocos segundos para actualizar). El otro script que consumía bastante CPU es el que genera el número de respuestas de cada comentario (Screenshot from 2013-03-17 19:48:01).

Ambos scripts son llamados desde Javascript, y cuando la página ya fue descargada, por lo que no son “bloqueantes”. La solución fue simple: hacer que esos scripts no bloqueantes y que no necesitan tiempos de respuestas de milésimas se segundos consulten al slave. Para ello aumentamos el tamaño del slave de small a medium (menos de 50 € adicionales al mes), y configuramos para lo usen.

Así logramos reducir la carga del máster de picos habituales superiores al 80%, y medias superiores al 60% durante horas de tráfico elevado, a picos que rara vez superan el 60%, y medias de alrededor del 30%:

Screenshot from 2013-03-17 19:55:12

Este es el consumo del slave (antes no llegaba al 10%, con una capacidad inferior):

Screenshot from 2013-03-17 20:10:33

En resumen: haciendo que las consultas que no son necesarias para generar la página inmediatamente consulten a un slave, en vez de al master Multi AZ, hizo que por unos 50€ más al mes prácticamente se duplique la capacidad del master.

Se podría explicar de otra forma: no hace falta que la base de datos Multi AZ esté configurado al máximo, posible, se ahorra mucho usando slaves, que son más baratos y muy fáciles de administrar. Tampoco hace falta que se haga un balanceo de carga entre el máster y el slave, basta con “particionar” las llamadas y hacer que los scripts que no forman parte del fast path, ni necesiten sincronía, usen un slave para sus consultas. Esta solución es mucho más sencilla y barata (no necesita slaves de la misma capacidad que el master) que un balanceo entre slaves y el master (también se podría balancear sólo entre slaves, y usar el master únicamente para updates e inserts, pero es algo más complicado, delicado, y no tan barato).

Cachea todo, ¡estúpido!

Justo cuando estaba dando la charla en Youzzing (en Palma Activa, aquí la presentación en PDF) se produjo la elección del nuevo Papa, lo que hizo que el tráfico se incrementase a valores record, llegamos a tener 10 servidores webs en marcha. Aunque los servidores tenían capacidad de sobra, daba muchos fallos 502, no había suficientes procesos disponibles para ejecutar los scripts. Miré la carga del master de la base de datos, y estaba a menos del 50%, pero cuando fui a ver el slave, estaba al 100%. Era éste el que bloqueaba los scripts durante demasiado tiempo y generaba los errores.

En cuanto llegué a casa (¡maldición, cómo tardó en llegar el taxi!, mientras esperaba bajo la lluvia) me puse a analizar, era el script del API para los botoncitos de Menéame en otros sitios el que producía esa saturación. Estaba generando varios miles de peticiones por segundo al “pobre” slave (de la mitad de capacidad del master), una de las principales responsables fue la página en directo de La Vanguardia, que se recargaba automáticamente cada pocos segundos, haciendo que todos los navegadores vuelvan a consultar al API de Menéame, sumando otra conexión a la base de datos. Desactivé el script momentáneamente (haciendo que devuelva siempre “no”), y vi que la carga se redujo inmediatamente a menos del 20%.

Inmediatamente me di cuenta de cuál era la solución: cachear los resultados durante unos pocos segundos (lo puse para 5 segundos) en el memcached/xcache [*] de cada instancia web, para cada URL que se consulta. Esto hace que se incremente algo el consumo de CPU en cada instancia web, pero de eso teníamos de sobra y su escalado es automático. También incrementa el uso de RAM, pensé que sería mucho -era mi miedo, se hacen muchas consultas de diferentes URLs-, pero luego pude comprobar que no llega ni a 1 MB por instancia.

[*] Podemos usar el memcached o una funcionalidad equivalente del xcache, es configurable. Usamos este último, porque reduce conexiones vía sockets, ahorrando algunos microsegundos de latencia

Screenshot from 2013-03-17 20:39:17

La modificación para cachear no me tomó mas de 15 minutos, y cuando la habilité, el consumo del script durante esas horas de alto tráfico no llegó ni al 15%.

En resumen, aunque parezca que tienes todo bajo control, siempre ocurrirá un evento que superará tus expectativas, y cuando se trata de consultas a la base de datos, esta es la parte más débil de la cadena. Es decir, si es posible y no provoca problemas es mejor  cachear los datos temporalmente en aquellos sitios que puedes escalar mejor.

Y mi pregunta es ¿por qué no lo hice antes? Hay que ser estúpido.

Nota: uso la palabra “particionado funcional” sin que sea un particionado de verdad, la base de datos es única y completa, pero no se me ocurrió otro nombre para describirlo y que se entienda la idea. Perdón, por si acaso.