
Este es el próximo artículo de la serie que describe cómo estamos aumentando la disponibilidad de nuestro servicio en Citymobil (puede leer las partes anteriores aquí:
parte 1 ,
parte 2 ,
parte 3 ). En otras partes, hablaré sobre los accidentes e interrupciones en detalle.
1. Mala versión: sobrecarga de la base de datos
Permítanme comenzar con un ejemplo específico de este tipo de interrupción. Implementamos una optimización: agregamos USE INDEX en una consulta SQL; tanto durante las pruebas como en la producción, aceleró las consultas cortas, pero las largas, disminuyeron. La desaceleración de las consultas largas solo se notó en la producción. Como resultado, muchas consultas paralelas largas provocaron que la base de datos permaneciera inactiva durante una hora. Estudiamos a fondo la forma en que funcionaba USE INDEX; lo describimos en el archivo Do's and Dont's y advertimos a los ingenieros contra el uso incorrecto. También analizamos la consulta y nos dimos cuenta de que recupera principalmente datos históricos y, por lo tanto, puede ejecutarse en una réplica separada para solicitudes históricas. Incluso si esta réplica se cae debido a una sobrecarga, el negocio seguirá funcionando.
Seguimos tropezando con los mismos problemas después, y en algún momento, decidimos abordar este asunto. Habíamos estudiado el código y movido todas las consultas que pudimos sin comprometer nuestro servicio a las réplicas. Las réplicas se dividieron en función de su nivel de criticidad, de modo que ninguna de ellas podía fallar y detener el servicio. Como resultado, se nos ocurrió una arquitectura con las siguientes bases de datos:
- base de datos maestra: para operaciones de escritura y consultas que son muy sensibles a la actualización de datos;
- réplicas de producción: para consultas cortas que son menos sensibles a la actualización de datos;
- réplicas de coeficientes de aumento de precios. Estas réplicas pueden tener 30-60 segundos de retraso; eso no es crucial ya que los coeficientes no cambian con tanta frecuencia y si esta réplica se cae, el servicio no dejará de funcionar; los precios simplemente no coincidirán completamente con el saldo de oferta y demanda;
- réplica para la configuración operativa y el centro de llamadas. Si falla, la empresa seguirá funcionando pero sin el soporte del cliente y del controlador, y no podremos alterar temporalmente algunas configuraciones;
- muchas réplicas para análisis y paneles de control ad hoc;
- Base de datos MPP (procesamiento masivo paralelo) para algunos análisis masivos con cortes completos en los datos históricos.
Esta arquitectura nos proporcionó un amplio espacio para el crecimiento y redujo una serie de bloqueos debido a consultas SQL no óptimas. Pero aún está lejos de ser perfecto. Estamos planeando implementar fragmentación, de modo que podamos escalar actualizaciones y eliminaciones y ser extremadamente sensibles a las consultas de actualización de datos. El margen de seguridad de MySQL no es infinito. Pronto necesitaremos artillería pesada en forma de una base de datos en memoria (por ejemplo, Tarantool). Definitivamente voy a hablar de eso en mis próximos artículos.
Mientras estábamos lidiando con el código y las consultas no óptimas, entendimos lo siguiente: cualquier no óptimo debería eliminarse antes de que se haya lanzado y no después. Esto disminuye el riesgo de interrupciones y esfuerzos de los equipos de ingeniería para la optimización. Si el código ya se ha implementado con algunas nuevas versiones además, es mucho más difícil optimizarlo. Como resultado, introdujimos una revisión de código obligatoria para la optimización. Lo llevan a cabo nuestros ingenieros más experimentados, nuestra fuerza de élite.
También comenzamos a recopilar los mejores métodos de optimización de código adecuados para nuestra realidad en Do's y Dont's. Se enumeran a continuación. Por favor, no tome estas prácticas como una verdad innegable y no intente replicarlas ciegamente. Cada método tiene sentido solo para una situación específica y un negocio específico. Este es solo un ejemplo para aclarar los detalles:
- Si una consulta SQL no depende del usuario (por ejemplo, el mapa de demanda del conductor con tarifas máximas y coeficientes de sobretensión; este mapa es el mismo para cualquier usuario de la aplicación del controlador), entonces esta consulta debe realizarse en un script cron con alguna frecuencia específica (una vez por minuto es suficiente en ese caso). El resultado debe escribirse en un caché (Memcached o Redis) que debe usarse en el código de producción.
- Si una consulta SQL opera con los datos cuyo retraso no es crucial para el negocio, entonces su resultado debe colocarse en un caché con algo de TTL (30 segundos, por ejemplo) y luego leerse del caché por código de producción.
- Si en una implementación de un método de servidor específico (en PHP u otro lenguaje del lado del servidor) decide realizar una consulta SQL, debe asegurarse de que los datos que necesita no hayan llegado con alguna otra consulta SQL o estén por llegar Su código a continuación.
- Igual que el anterior va a las solicitudes a un caché. Un caché es rápido pero sigue siendo una base de datos. Por lo tanto, también se puede sobrecargar. Un error común es que usted piensa que un caché es una especie de variable en memoria normal y lo usa como una variable. Pero el acceso a él implica un viaje de ida y vuelta a la red y genera una carga de trabajo para Redis o Memcached. Por lo tanto, si los datos ya han llegado del caché, entonces no solo tome del caché lo que ya se ha tomado.
- Si durante el procesamiento de una solicitud web (nuevamente en PHP o en cualquier otro lenguaje) necesita llamar a una función, debe asegurarse de que no se realizarán consultas SQL adicionales ni acceso a la memoria caché. Si tal llamada a la función es inevitable, debe asegurarse de que no se pueda modificar o que su lógica no se haya roto para evitar consultas innecesarias en la base de datos / caché.
- Si es necesario realizar una consulta SQL, debe estar absolutamente seguro de que los campos que necesita no se pueden agregar a consultas ya existentes en su código arriba o abajo.
2. Mala operación manual
Ejemplos de estos accidentes:
- ALTER incorrecto que sobrecargó la base de datos o causó demoras en la réplica;
- DROP incorrecto (p. ej., encontramos un error en MySQL que bloqueó la base de datos al soltar una tabla);
- una consulta pesada en un maestro, hecha manualmente por error;
- se estaba configurando un servidor web a pesar de que estaba bajo la carga de trabajo real, mientras que pensábamos que estaba fuera de servicio.
Para minimizar las interrupciones debido a estas razones, tenemos que investigar la naturaleza de un accidente cada vez que ocurre. Todavía no hemos descubierto una regla general. Nuevamente, veamos algunos ejemplos específicos. Los coeficientes de sobretensión (la tarifa del taxi en el momento y el lugar de alta demanda se multiplica por ellos) dejaron de funcionar en algún momento. La razón fue que había un script de Python trabajando en un servidor de réplica de base de datos de donde se tomaron los datos para el cálculo de coeficientes y el script usó toda la memoria y la réplica se cayó. El guión había estado ejecutándose por un tiempo; estaba funcionando directamente en la réplica por conveniencia. El problema se resolvió reiniciando el script. Se sacaron las siguientes conclusiones: no ejecute scripts extranjeros en un servidor de base de datos (fue escrito en Do's and Don't's; de lo contrario, sería un tiro vacío), Supervise el uso de memoria en un servidor de base de datos y alerta por SMS si ese servidor está a punto de quedarse sin memoria.
Siempre es esencial sacar conclusiones y no sentirse cómodo en el tipo de situación "vi el problema, lo arreglé, lo olvidé". El servicio de calidad solo se puede ofrecer si uno se va con una conclusión. Además de eso, las alertas por SMS son críticas: aumentan el nivel de calidad del servicio, no lo dejan caer y nos permiten aumentar su confiabilidad. Como un alpinista que llega a una posición estable y luego se levanta a otra posición estable, pero esta vez solo más alto.
El monitoreo y las alertas no son visibles, pero actúan como ganchos de hierro que cortan la roca de lo desconocido y nos impiden caer por debajo de nuestro acuerdo de nivel de servicio de que estamos aumentando continuamente.
3. huevo de pascua
Lo que llamamos "un huevo de Pascua" es una mina de acción retardada que aún no hemos tropezado, a pesar de que existe desde hace un tiempo. Fuera de este artículo, este término se usa para las características no documentadas creadas a propósito. En nuestro caso, no es una característica en absoluto, sino más bien un error que actúa como una bomba de tiempo y aparece como un efecto secundario de alguna actividad bien intencionada.
Por ejemplo:
- sobrellenado de
auto_increment
de 32 bits;
- no optimismo del código / configuración, desencadenado por una alta carga de trabajo;
- réplica retrasada por una consulta no óptima provocada por un nuevo patrón de uso o por una carga de trabajo más pesada;
- réplica retrasada por una operación ACTUALIZACIÓN no óptima en el maestro que fue causada por un nuevo patrón de carga de trabajo y retrasó la replicación.
Otro tipo popular de huevo de Pascua es el código no óptimo; para ser más específico: consulta SQL no óptima. La tabla solía ser más pequeña y la carga de trabajo era más ligera: la consulta funcionó bien. Con el crecimiento lineal en la tabla de tiempos y el aumento de la carga de trabajo lineal en el tiempo, el consumo de recursos por parte de un sistema de gestión de bases de datos creció de forma cuadrática. Por lo general, eso lleva a un efecto negativo drástico: es como si todo estuviera bien y de repente, ¡vaya!
Escenarios más raros: combinación de errores y huevos de Pascua. Un lanzamiento con un error condujo a la ampliación de una tabla de base de datos y aumentó un número de filas de tabla de un tipo específico, mientras que un huevo de Pascua ya existente causó la sobrecarga de la base de datos debido a las consultas más lentas a esta tabla expandida.
Sin embargo, solíamos tener algunos huevos de Pascua no relacionados con la carga de trabajo. Por ejemplo, un campo de
auto_increment
32 bits en MYSQL. Después de un poco más de 2 mil millones de filas, los insertos fallan. Por lo tanto, en el mundo moderno, debemos usar solo campos de
auto_increment
64 bits. Aprendimos bien esa lección.
¿Cómo lidiar con los huevos de Pascua? La respuesta suena sencilla: a) busca los huevos viejos, b) no permitas que aparezcan los nuevos. Estamos tratando de hacer las dos cosas. La búsqueda de los viejos huevos va de la mano con nuestra optimización continua del código. Designamos a dos de los ingenieros más experimentados para realizar la optimización casi a tiempo completo. Encuentran consultas en slow.log que utilizan más los recursos de bases de datos; optimizan estas consultas y el código que las rodea. Disminuimos la posibilidad de que surjan nuevos huevos a través de la prueba de cada compromiso de optimización realizado por los ingenieros de sensei mencionados anteriormente. Su tarea es señalar los errores que afectan el rendimiento, sugerir la forma de mejorar las cosas y transmitir este conocimiento a otros ingenieros.
En algún momento, justo después de encontrar otro huevo de Pascua, nos dimos cuenta de que era bueno buscar consultas lentas, pero también deberíamos haber buscado las consultas que parecen ser lentas pero funcionan rápido. Estos son los próximos contendientes por estrellar todo en caso de otra tabla de crecimiento explosivo. El ejemplo estúpido pero obvio aquí es una consulta que escanea por completo una tabla de 10 filas sin usar índices en absoluto. Funcionará rápido por el momento. Sin embargo, cuando la tabla es lo suficientemente grande, la consulta eliminará la base de datos. Ese es el huevo de Pascua.
4. Causas externas
Estas son las causas que parecemos incapaces de controlar muy bien. Dicho de otra manera, esas son las causas que solo pueden mitigarse pero no eliminarse. Por ejemplo:
- Limitando nuestras solicitudes por un proveedor de servicios de mapas. Se puede mitigar mediante el control de uso del servicio, el cumplimiento de un nivel de carga de trabajo específico, la planificación del aumento de la carga de trabajo de antemano y la compra de la expansión del servicio. Sin embargo, no podemos prescindir de los mapas.
- Falla de red en un centro de datos. Se puede mitigar colocando la copia del servicio en un centro de datos de respaldo. Sin embargo, no podemos prescindir de un centro de datos físico o en la nube.
- Servicio de pago inicial. Se puede mitigar mediante servicios de pago de respaldo. Sin embargo, no podemos prescindir de los pagos.
- Bloqueo errante del tráfico por un servicio de protección DDoS. Puede mitigarse desactivando el servicio de protección DDoS de forma predeterminada, activándolo solo en caso de un ataque DDoS. Sin embargo, no podemos prescindir de la protección DDoS.
Dado que incluso la mitigación de una causa externa es un esfuerzo largo y costoso, comenzamos a recopilar estadísticas de accidentes causados por razones externas y esperamos la acumulación de masa crítica. No tenemos una receta para definir una masa crítica. Se trata de mera intuición. Por ejemplo, si estuviéramos completamente caídos cinco veces debido, por ejemplo, a los problemas del servicio de protección DDoS, entonces con cada tiempo de inactividad posterior, la necesidad de una alternativa se volvería cada vez más aguda.
Por otro lado, si de alguna manera podemos hacer que todo funcione con un servicio externo no disponible, definitivamente lo haremos. El análisis post mortem de cada corte nos ayuda aquí. Siempre debe haber una conclusión. Lo que significa que nos guste o no, siempre encontramos una solución.
En la parte final, voy a hablar sobre un tipo más de interrupciones y las conclusiones que hicimos sobre ellas, cómo modificamos el proceso de desarrollo, qué automatización introdujimos. Estén atentos!