
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í y
aquí ). En otras partes, hablaré sobre los accidentes e interrupciones en detalle. Pero primero permítanme resaltar algo de lo que debería haber hablado en el primer artículo pero no lo hice. Me enteré por los comentarios de mis lectores. Este artículo me da la oportunidad de solucionar este inconveniente molesto.
1. Prólogo
Un lector me hizo una pregunta muy justa: "¿Qué tiene de complicado el backend del servicio de transporte?" Esa es una buena pregunta. El verano pasado, me hice esa misma pregunta antes de comenzar a trabajar en Citymobil. Estaba pensando: "eso es solo un servicio de taxi con su aplicación de tres botones". ¿Qué tan difícil puede ser? Resultó ser un producto de muy alta tecnología. Para aclarar un poco de qué estoy hablando y qué gran cosa tecnológica es, les voy a contar algunas instrucciones sobre productos en Citymobil:
- Precios. Nuestro equipo de fijación de precios aborda el problema del mejor precio de viaje en cada punto y en cada momento. El precio está determinado por la predicción del equilibrio de la oferta y la demanda basada en estadísticas y algunos otros datos. Todo se realiza mediante un servicio complicado y en constante desarrollo basado en el aprendizaje automático. Además, el equipo de fijación de precios se ocupa de la implementación de varios métodos de pago, cargos adicionales al completar un viaje, devoluciones de cargo, facturación, interacción con socios y conductores.
- Despacho de pedidos. ¿Qué auto completa el pedido del cliente? Por ejemplo, una opción de elegir el vehículo más cercano no es la mejor en términos de maximización de varios viajes. La mejor opción es hacer coincidir los autos y los clientes para maximizar el número de viajes considerando una probabilidad de que este cliente específico cancele su pedido en estas circunstancias específicas (porque la espera es demasiado larga) y una probabilidad de que este conductor específico cancele o sabotee el pedido ( por ejemplo, porque la distancia es demasiado grande o el precio es demasiado pequeño).
- Geo. Todo sobre la búsqueda y sugerencia de direcciones, puntos de recogida, ajustes de la hora estimada de llegada (nuestros socios de suministro de mapas no siempre nos brindan información precisa de ETA con margen para el tráfico), aumento de precisión de geocodificación directa e inversa, aumento de precisión del punto de llegada del automóvil. Hay muchos datos, muchos análisis, muchos servicios basados en aprendizaje automático.
- Antifraude La diferencia en el costo del viaje para un pasajero y un conductor (por ejemplo, en viajes cortos) crea un incentivo económico para los intrusos que intentan robar nuestro dinero. Lidiar con el fraude es algo similar a lidiar con el correo no deseado: tanto la precisión como el retiro son muy importantes. Necesitamos bloquear el número máximo de fraudes (retiro del mercado), pero al mismo tiempo no podemos aceptar buenos usuarios por fraudes (precisión).
- El equipo de incentivos para conductores supervisa el desarrollo de todo lo que pueda aumentar el uso de nuestra plataforma por parte de los conductores y la lealtad de los conductores debido a los diferentes tipos de incentivos. Por ejemplo, complete X viajes y obtenga dinero extra Y. O compre un turno para Z y conduzca sin comisión.
- Aplicación de controlador de back-end. Lista de pedidos, mapa de demanda (muestra a un conductor a dónde ir para maximizar sus ganancias), cambios de estado, sistema de comunicación con los conductores y muchas otras cosas.
- Backend de la aplicación cliente (esta es probablemente la parte más obvia y eso es lo que la gente suele llamar "backend de taxi"): colocación de pedidos, información sobre el estado del pedido, proporcionar el movimiento de pequeños autos en el mapa, consejos de backend, etc.
Esto es solo la punta del iceberg. Hay mucha más funcionalidad. Hay una gran parte submarina del iceberg detrás de lo que parece ser una interfaz bastante simple.
Y ahora volvamos a los accidentes. Seis meses de registro del historial de accidentes dieron como resultado la siguiente clasificación:
- mala versión: 500 errores internos del servidor;
- mala versión: sobrecarga de la base de datos;
- desafortunada interacción manual de operación del sistema;
- Huevos de pascua;
- razones externas;
- mala versión: funcionalidad rota.
A continuación, detallaré las conclusiones que hemos sacado con respecto a nuestros tipos de accidentes más comunes.
2. Mala versión: 500 errores internos del servidor
Nuestro backend está escrito principalmente en PHP, un lenguaje interpretado débilmente escrito. Lanzaríamos un código que se bloqueó debido al error en el nombre de la clase o función. Y ese es solo un ejemplo cuando se produce un error 500. También puede ser causado por un error lógico en el código; rama equivocada fue liberada; la carpeta con el código se eliminó por error; los artefactos temporales necesarios para la prueba se dejaron en el código; la estructura de las tablas no se modificó de acuerdo con el código; los scripts cron necesarios no se reiniciaron ni se detuvieron.
Poco a poco abordamos este problema por etapas. Los viajes perdidos debido a un mal lanzamiento son obviamente proporcionales a su tiempo en producción. Por lo tanto, debemos hacer nuestro mejor esfuerzo y asegurarnos de minimizar el tiempo de mala producción en producción. Cualquier cambio en el proceso de desarrollo que reduzca un tiempo promedio de tiempo de funcionamiento de lanzamiento incorrecto incluso en 1 segundo es bueno para el negocio y debe implementarse.
Mala liberación y, de hecho, cualquier accidente en la producción tiene dos estados que llamamos "una etapa pasiva" y "una etapa activa". Durante la etapa pasiva todavía no tenemos conocimiento de un accidente. La etapa activa significa que ya lo sabemos. Un accidente comienza en la etapa pasiva; con el tiempo pasa a la etapa activa, es cuando lo descubrimos y comenzamos a abordarlo: primero lo diagnosticamos y luego lo arreglamos.
Para reducir la duración de cualquier interrupción, necesitamos reducir la duración de las etapas activas y pasivas. Lo mismo ocurre con un mal lanzamiento, ya que se considera una especie de interrupción.
Comenzamos a analizar el historial de solución de problemas de interrupciones. Los malos lanzamientos que experimentamos cuando recién comenzamos a analizar los accidentes causaron un tiempo de inactividad promedio de 20-25 minutos (total o parcial). La etapa pasiva generalmente tomaría 15 minutos, y la activa, 10 minutos. Durante la etapa pasiva, recibiríamos quejas de los usuarios que fueron procesadas por nuestro centro de llamadas; y después de un umbral específico, el centro de llamadas se quejaría en un chat de Slack. A veces, uno de nuestros colegas se quejaba de no poder tomar un taxi. La queja del colega indicaría un problema grave. Después de que una mala versión entró en la etapa activa, comenzamos el diagnóstico del problema, analizando las últimas versiones, varios gráficos y registros para averiguar la causa del accidente. Al determinar las causas, retrocederíamos si la versión incorrecta era la última o si realizaríamos una nueva implementación con la confirmación revertida.
Este es el mal proceso de manejo de la versión que estábamos destinados a mejorar.
Etapa pasiva: 20 minutos.
Etapa activa: 10 minutos.
3. Reducción de etapa pasiva
En primer lugar, notamos que si un lanzamiento incorrecto iba acompañado de 500 errores, podríamos decir que se había producido un problema incluso sin las quejas de los usuarios. Afortunadamente, los 500 errores se registraron en New Relic (este es uno de los sistemas de monitoreo que usamos) y todo lo que tuvimos que hacer fue agregar notificaciones por SMS e IVR sobre exceder un número específico de 500 errores. El umbral se reduciría continuamente a medida que pasara el tiempo.
El proceso en tiempos de accidente se vería así:
- Un ingeniero implementa una versión.
- El lanzamiento lleva a un accidente (gran cantidad de 500).
- Mensaje de texto recibido.
- Ingenieros y desarrolladores comienzan a investigarlo. A veces no de inmediato, pero en 2-3 minutos: el mensaje de texto puede retrasarse, los sonidos del teléfono pueden estar apagados; y, por supuesto, el hábito de reacción inmediata al recibir este texto no se puede formar de la noche a la mañana.
- La etapa activa del accidente comienza y dura los mismos 10 minutos que antes.
Como resultado, la etapa activa del tipo de accidente "Mala versión: 500 errores internos del servidor" comenzaría 3 minutos después de una liberación. Por lo tanto, la etapa pasiva se redujo de 15 minutos a 3.
Resultado:
Etapa pasiva: 3 minutos.
Etapa activa: 10 minutos.
4. Reducción adicional de una etapa pasiva
A pesar de que la etapa pasiva se redujo a 3 minutos, todavía nos molestó más que la activa, ya que durante la etapa activa estábamos haciendo algo tratando de solucionar el problema, y durante la etapa pasiva el servicio estaba total o parcialmente inactivo, y nosotros fueron absolutamente despistados.
Para reducir aún más la etapa pasiva, decidimos sacrificar 3 minutos del tiempo de nuestros ingenieros después de cada lanzamiento. La idea era muy simple: desplegábamos código y durante tres minutos después buscábamos 500 errores en New Relic, Sentry y Kibana. Tan pronto como vimos un problema allí, asumiremos que está relacionado con el código y comenzamos a solucionar el problema.
Elegimos este período de tres minutos en función de las estadísticas: a veces los problemas aparecían en los gráficos en 1-2 minutos, pero nunca más tarde que en 3 minutos.
Esta regla se agregó a lo que se debe y no se debe hacer. Al principio, no siempre se siguió, pero con el tiempo nuestros ingenieros se acostumbraron a esta regla como lo hicieron con la higiene básica: cepillarse los dientes por la mañana también lleva algún tiempo, pero aún es necesario.
Como resultado, la etapa pasiva se redujo a 1 minuto (las gráficas todavía llegaban tarde a veces). También redujo la etapa activa como una buena ventaja. Porque ahora un ingeniero enfrentaría el problema preparado y estaría listo para revertir su código de inmediato. Aunque no siempre ayudó, ya que el problema podría haber sido causado por una versión implementada simultáneamente por otra persona. Dicho esto, la etapa activa en promedio se redujo a cinco minutos.
Resultado:
Etapa pasiva: 1 minutos.
Etapa activa: 5 minutos.
5. Reducción adicional de una etapa activa
Nos quedamos más o menos satisfechos con la etapa pasiva de 1 minuto y comenzamos a pensar en cómo reducir aún más una etapa activa. En primer lugar, centramos nuestra atención en el historial de interrupciones (¡resulta ser una piedra angular en un edificio de nuestra disponibilidad!) Y descubrimos que, en la mayoría de los casos, no cancelamos un lanzamiento de inmediato ya que no sabemos a qué versión deberíamos recurrir: hay muchas versiones paralelas. Para resolver este problema, introdujimos la siguiente regla (y la escribimos en lo que se debe y no se debe hacer): justo antes de un lanzamiento, se debe notificar a todos en un chat de Slack sobre lo que se va a implementar y por qué; En caso de accidente, se debe escribir: "¡Accidente, no despliegue!" También comenzamos a notificar a los que no leen el chat sobre los comunicados por SMS.
Esta simple regla redujo drásticamente el número de liberaciones durante un accidente en curso, disminuye la duración de la resolución de problemas y reduce la etapa activa de 5 minutos a 3.
Resultado:
Etapa pasiva: 1 minutos.
Etapa activa: 3 minutos.
6. Reducción aún mayor de una etapa activa
A pesar del hecho de que publicamos advertencias en el chat con respecto a todos los lanzamientos y accidentes, las condiciones de carrera todavía ocurrían a veces: alguien publicó sobre un lanzamiento y otro ingeniero se estaba desplegando en ese mismo momento; o ocurrió un accidente, escribimos sobre eso en el chat pero alguien acababa de implementar su código. Tales circunstancias prolongaron la resolución de problemas. Para resolver este problema, implementamos la prohibición automática de versiones paralelas. Fue una idea muy simple: durante 5 minutos después de cada versión, el sistema CI / CD prohíbe otra implementación para cualquier persona que no sea la última autor de la versión (para que pueda retroceder o implementar una revisión si es necesario) y varios desarrolladores con experiencia (en caso de emergencia). Más que eso, el sistema CI / CD evita despliegues en tiempo de accidentes (es decir, desde el momento en que llega la notificación sobre el comienzo del accidente y hasta la llegada de la notificación sobre su finalización).
Entonces, nuestro proceso comenzó a verse así: un ingeniero implementa una versión, monitorea los gráficos durante tres minutos y, después de eso, nadie puede implementar nada durante otros dos minutos. En caso de que ocurra un problema, el ingeniero revierte la liberación. Esta regla simplificó drásticamente la resolución de problemas, y la duración total de las etapas activas y pasivas se redujo de 3 + 1 = 4 minutos a 1 + 1 = 2 minutos.
Pero incluso un accidente de dos minutos fue demasiado. Es por eso que seguimos trabajando en nuestra optimización de procesos.
Resultado:
Etapa pasiva: 1 minuto.
Etapa activa: 1 minuto.
7. Determinación automática de accidentes y reversión
Habíamos estado pensando durante un tiempo cómo reducir la duración de los accidentes causados por las malas liberaciones. Incluso intentamos obligarnos a mirar en la
tail -f error_log | grep 500
tail -f error_log | grep 500
. Pero al final, optamos por una solución automática drástica.
En pocas palabras, es una reversión automática. Tenemos un servidor web separado y lo cargamos a través del equilibrador 10 veces menos que el resto de nuestros servidores web. Cada versión sería implementada automáticamente por los sistemas CI / CD en este servidor separado (lo llamamos
preprod, pero a pesar de su nombre recibiría una carga real de los usuarios reales). Entonces el script realizará
tail -f error_log | grep 500
tail -f error_log | grep 500
. Si dentro de un minuto no hubo un error 500, CI / CD desplegaría la nueva versión en producción en otros servidores web. En caso de que hubiera errores, el sistema lo revierte todo. En el nivel de equilibrador, todas las solicitudes dieron como resultado 500 errores en preprod que se volverían a enviar en uno de los servidores web de producción.
Esta medida redujo el impacto de 500 errores libera a cero. Dicho esto, solo en caso de errores en los controles automáticos, no abolimos nuestra regla de vigilancia gráfica de tres minutos. Eso se trata de malas versiones y 500 errores. Pasemos al siguiente tipo de accidentes.
Resultado:
Etapa pasiva: 0 minutos.
Etapa activa: 0 minutos.
En otras partes, hablaré sobre otros tipos de interrupciones en la experiencia de Citymobil y entraré en detalles sobre cada tipo de interrupción; También le contaré sobre las conclusiones que sacamos sobre las interrupciones, cómo modificamos el proceso de desarrollo, qué automatización introdujimos. Estén atentos!