Si puedes, parcheame: cómo estamos depurando la producción. Parte 2

En la primera parte de mi artículo, hablé sobre cómo en Badoo creamos la primera versión del sistema de parches. En resumen, necesitábamos encontrar una manera de corregir errores graves directamente en la producción, accesible para todos los desarrolladores. Sin embargo, la primera versión no estuvo exenta de inconvenientes: utilizamos un método de diseño peculiar, que no garantizaba la atomicidad del diseño de los parches y la consistencia del código.

En esta parte del artículo, hablaré sobre una nueva forma de diseñar el código que se nos ocurrió al tratar de resolver nuestros problemas, y cómo nuestro sistema de parches se transformó con él.


Imagen: fuente

Solución universal: kit de implementación multiversional


Después de otra revisión de nuestro sistema Jura, youROCK Nasretdinov declaró que tiene una idea sobre cómo resolver todos nuestros problemas. Todo lo que pidió fue mucho tiempo para rehacer el sistema de diseño. Así es como apareció el concepto del Kit de implementación multiversional o, en la gente común, el MDK (Jura lo comparó con otras formas de diseño de código en su informe sobre HighLoad ++ ).

El nuevo sistema fue diseñado para cambiar nuestro procedimiento de diseño. Para aquellos que no han leído mi primera parte del artículo, les diré brevemente cómo se ve el proceso de implementación: primero recopilamos todos los archivos necesarios en un directorio, luego guardamos y entregamos el estado del directorio a los servidores.

Antes de la era MDK, usábamos dispositivos de bloque (es decir, imágenes del sistema de archivos) llamados bucles para almacenar y entregar. El directorio se copió a un bucle vacío, se archivó y se envió a los servidores.

En el nuevo sistema, estamos versionando no todo el directorio, sino cada archivo individualmente para que la versión del archivo se correlacione claramente con su contenido. Para los directorios hay mapas (mapas): archivos especiales en los que se graban las versiones de todos los archivos del directorio. Estas tarjetas también están versionadas, y todo se ve así:



¿Te resulta familiar? Así es como se organizan los objetos en Git (puede leer sobre esto aquí , pero esto no es necesario para comprender el artículo).

Para el control de versiones, utilizamos los primeros ocho caracteres del hash MD5 (de su representación hexadecimal, para ser precisos), tomados del contenido del archivo. Esta versión se escribe al final del nombre del archivo o al comienzo del nombre del mapa (para que pueda distinguir el archivo del mapa del mapa de la versión generada):



La versión del código es la versión del mapa del directorio raíz www. Para encontrar el mapa actual, tenemos un enlace simbólico (enlace simbólico) current.map.

¿Por qué no usar git?
A pesar de que MDK toma prestadas ideas de Git, tienen algunas diferencias. Lo más importante es cómo se almacenan los archivos en el directorio de trabajo (es decir, en las máquinas). Si Git almacena solo una versión actual, allí, entonces MDK contiene todas las versiones disponibles de los archivos allí. Al mismo tiempo, solo un enlace simbólico current.map apunta a la versión actual del código, que utiliza la carga automática en su trabajo y que se puede cambiar atómicamente. A modo de comparación, Git utiliza git-checkout para cambiar la versión, que a su vez cambia los archivos y no es atómica.

Construir con MDK


Se necesita MDK para guardar el estado del directorio al final del ensamblaje. Para hacer esto, tenemos un lugar especial, que llamamos un repositorio, un repositorio de todas las versiones de archivos que son de valor para nosotros (es decir, que podemos querer descomponer). Cuando los nuevos contenidos del directorio están listos, calculamos las versiones de todos los archivos que contiene y reportamos los que faltan al repositorio.

Diseño con MDK


Durante el diseño de cada uno de los servidores receptores, ejecutamos un script que verifica si todos los archivos necesarios están en el servidor y solicita los que faltan en el repositorio. Solo podemos cambiar la versión a una nueva cambiando el enlace simbólico current.map.

Cómo debería resolver nuestros problemas


Se suponía que si solo unos pocos archivos cambiaban en la nueva versión, su ensamblaje y diseño usando el nuevo sistema debería ser al menos comparable en tiempo al diseño del parche como archivos separados. Si es así, para cada parche solo generaremos una nueva versión.

Implementación de MDK


MDK tenía un inconveniente: en las máquinas finales, el nombre de cada archivo debería tener su versión. Esto es lo que le permite almacenar muchas versiones de un archivo en un directorio a la vez, pero no le permite incluir include user.php del código; debe especificar una versión específica. Agregue a esto los diversos errores que bien podrían permanecer en el código del sistema de diseño, el nuevo algoritmo de diseño, que era más complicado que el anterior, y quedará claro por qué decidimos implementar el nuevo sistema en pequeños pasos. Comenzamos literalmente con uno o dos servidores y ampliamos gradualmente su lista, corrigiendo simultáneamente los problemas que surgen.

Dado que cambiar a un nuevo sistema debería haber llevado mucho tiempo, tuvimos que pensar en cómo funcionarían nuestros parches durante el período de transición. En ese momento, para el diseño de parches, utilizamos la utilidad de escritura automática mscp, que presentaba los archivos de uno en uno. Le enseñamos a reemplazar los archivos actuales en los servidores con MDK por adelantado, pero no pudimos agregar un nuevo archivo a dichos servidores (porque tuve que cambiar el mapa de archivos). No quería presentar una solución intermedia muy complicada, porque íbamos a un futuro brillante, donde no se necesita mscp. Como resultado, tuve que soportar este problema. En general, durante el período de transición, los desarrolladores lograron sufrir, pero ahora nos parece que valió la pena.

No confíes en nadie



Imagen: fuente

Probablemente, la pregunta será lógica, pero ¿habrá una colisión de versiones en MDK (es decir, una situación en la que dos archivos con diferentes contenidos tienen asignada la misma versión)?

De hecho, estamos bastante bien protegidos de este tipo de error. Nombramos { }.{} archivos de esta manera: { }.{} , lo que significa que deben coincidir mucho más de ocho caracteres para que se produzca un error.

Pero una vez, sin embargo, algo salió mal. Después del siguiente cálculo, notamos un número creciente de errores con el código HTTP 404 (archivo no encontrado). Una pequeña investigación mostró que faltaban algunos de los archivos estáticos. Resultó que presentamos un mapa estático muy antiguo y proporcionamos enlaces a archivos que aún no deberían estar en los servidores. ¿Pero de dónde vino esta carta? En la primera parte del artículo, noté que la estática se descompone por un proceso separado, y solo el mapa de versiones sale con el código PHP. Cuando generamos una nueva versión de MDK, informamos las versiones faltantes de los archivos al repositorio, del cual no se elimina nada (hay mucho espacio, no nos importa). Y a menudo damos todo lo mejor a la puesta en escena y, por lo tanto, el mapa de versiones estáticas es uno de esos archivos que cambian con más frecuencia que otros. Todo esto llevó al hecho de que nos enfrentamos a una colisión. Después de verificar la versión, MDK decidió que todo estaba bien, porque ya existe un archivo de esa versión, y lo distribuyó en los servidores. Es bueno que hayamos descubierto el error rápidamente.

Ahora, además de la versión, verificamos el tamaño del archivo: si es el mismo en el repositorio, lo más probable es que sea el mismo archivo. En el peor de los casos, tendremos una historia para un nuevo artículo.

MDK - Ladrón de Navidad



Imagen: fuente

Y quiero contarte sobre otro error, porque es al menos divertido. Es fácil adivinar que tuvimos un proceso de limpieza de versiones antiguas de archivos en servidores de destino. En un intento por resolver rápidamente uno de los problemas, tomamos una decisión fatídica: establecimos el período de limpieza en un día (en lugar de siete, como era antes). Funcionó, y el problema desapareció. Incluso vivimos por un tiempo.

A eso de las cinco de la mañana del domingo, sonó un teléfono en mi habitación y sonó el monitor de guardia: “Los guiones no funcionan para nosotros. Dicen que ya sabes cuál es el problema ". Para mí, sonaba como "En la oficina, el exprimidor se quemó. Dicen que sabes cuál es el problema ". Solo conocía los principios de nuestro marco de secuencias de comandos a partir de artículos e historias, no tenía ninguna "relación personal" con él, y aún más, nunca lo reparé. Pero me subí a los servidores para averiguar qué estaba sucediendo, y descubrí que el problema estaba realmente "de nuestro lado": simplemente no había código en los servidores.

Subí el código nuevamente, y funcionó. El error, por cierto, resultó ser primitivo: el sábado, no se presentó una sola versión nueva de MDK, y el script de limpieza, como resultó, no realizó ninguna comprobación para no eliminar la versión actual. Como resultado, a las cinco de la mañana él (en un horario) eliminó el código de todos los servidores. Después de esta historia, nos dimos cuenta de que con la configuración anterior, aparecería en vacaciones de 7 días, por ejemplo, en las vacaciones de Año Nuevo, solo en la víspera de Navidad. "Cristo nació, el código se fue", durante mucho tiempo pudimos escuchar esta broma.

Nuevo sistema de parches


Al final, presentamos un nuevo sistema de diseño, y es hora de rehacer el sistema de parches. Ya no era necesario mscp y no era necesario evitar generar nuevas versiones. Primero, cambiamos el ciclo de vida del parche. Ahora, después de confirmar los cambios, vuelve al desarrollador, que toma una decisión cuando el parche está listo para el cálculo. Hace clic en el botón Implementar, después de lo cual agregamos el parche para dominar, generar e implementar una nueva versión de MDK. La participación del desarrollador en esta etapa ya no es necesaria.

Hemos logrado una muy buena velocidad de diseño: los cambios llegan a los servidores literalmente en un minuto. Para hacer esto, sin embargo, tuvimos que recurrir a un par de trucos: por ejemplo, todavía no generamos estadísticas o traducciones; en su lugar, tomamos la versión de la última compilación descompuesta. Debido a esto, mantenemos una restricción en los parches para archivos JS y CSS.

Los experimentos


Realmente logramos resolver todos los problemas que teníamos antes. Ya no necesita pensar en cómo formar correctamente los cambios, lo que no causará dificultades con el diseño de archivo por archivo, simplemente no toque las estadísticas, y todo funcionará.

Pero apareció una nueva dificultad. Anteriormente, permitíamos a los desarrolladores publicar sus cambios en uno o más servidores, solo para asegurarnos de que todo funcionaría con ellos. Con el nuevo sistema, esta característica desapareció, porque master ahora se ha convertido en la versión actual para todos los servidores, sin excepción.


Imagen: fuente

Debido a esto, ha aparecido un nuevo requisito para el sistema de parches: necesita la capacidad de verificar sus cambios en un pequeño número de servidores sin agregar cambios al maestro. Llamamos a los nuevos experimentos de funcionalidad.

Para el desarrollador, el proceso se ve más o menos así: después de recibir la actualización, una nueva página está disponible en la interfaz del sistema de parches donde puede seleccionar los servidores en los que desea experimentar. Puede ser un grupo de servidores, un servidor o cualquier combinación que nuestro sistema entienda. El sistema implementa el parche y notifica al desarrollador. Al mismo tiempo, aparece un registro de los últimos errores de los servidores afectados en la página.

No limitamos a los desarrolladores de ninguna manera; pueden crear experimentos en los mismos servidores. Uno puede experimentar en un grupo de 10%, el otro en todo el grupo.

Esto fue posible debido al hecho de que teníamos la misma "versión de parches" que tanto nos faltaba. Esta es una versión que en teoría puede ser exclusiva de cada servidor. Parece una cadena de identificadores separados por comas, por ejemplo, "32,45,79". Esto significa que el servidor debe tener todos los cambios del asistente y los parches numerados 32, 45 y 79. Para cada versión, generamos nuestra propia versión de MDK. Tomamos los últimos cambios de la rama principal y luego aplicamos secuencialmente cada uno de los parches. Si surge un conflicto durante la generación de una de las versiones, simplemente cancelamos el experimento para el último parche y notificamos al desarrollador.

Archivos generados


Desde el primer día de la existencia del sistema de parches, fuimos al truco: nos negamos a generar estática para que los cambios lleguen a los servidores lo más rápido posible. Por supuesto, realmente queríamos tener la oportunidad de cambiar el código JS de la misma manera que cambiamos el código PHP, pero todos los intentos de construir este proceso no tuvieron éxito.

Hace unos seis meses, volvimos nuevamente a este tema. Propósito: necesita cambiar la estática, pero no puede sacrificar la velocidad del diseño del código PHP. El problema principal: un ensamblaje completo lleva ocho minutos. Que hacer

Tienes que comprometerte. Comenzamos con el hecho de que el código JS no puede presentarse como parte de los experimentos. Esto debería ahorrar mucho tiempo: solo mantenga una versión de las estadísticas actualizadas en lugar de generar docenas de versiones diferentes para diferentes grupos de máquinas. Pero aún es mucho tiempo. ¿Qué más puedes salvar? No descubrimos cómo reducir el tiempo, pero decidimos que no habría ningún problema si el ensamblaje no bloqueaba el diseño del código PHP.

Comenzamos a generar estadísticas de forma asincrónica. Con cambios en los archivos JS o CSS, comenzamos un proceso separado que crea un nuevo mapa de las versiones estáticas. El proceso de ensamblar código PHP al comienzo del trabajo verifica si hay un nuevo mapa estático y, si lo hay, lo recoge y lo coloca en todos los servidores. ¿Resolviste el problema? En la práctica Con este enfoque, optamos por una nueva limitación: no puede cambiar el código JS y PHP en un parche, porque descomponemos estos cambios de forma asincrónica y no podemos garantizar que estarán en las máquinas al mismo tiempo.

Resumen


Estamos muy satisfechos con la actualización. No fue fácil para nosotros, pero hizo que nuestro sistema fuera mucho más confiable. Los desarrolladores encontraron una aplicación alternativa para experimentos: con ellos puede recopilar fácilmente registros específicos de un par de servidores sin agregar sus cambios al maestro.

Todavía tenemos ideas para mejorar el sistema, para cuya implementación todavía no hay suficiente tiempo. Por ejemplo, queremos rehacer el proceso de creación de un parche y agregar la capacidad de cambiar los archivos JS al mismo tiempo que el código principal para eliminar las últimas restricciones.

Todos los días publicamos unos 60 parches, a veces hay varias veces más, por ejemplo, durante el desarrollo de alguna funcionalidad que hasta ahora solo está disponible para los probadores. Alrededor de un tercio de los parches pasan por experimentos antes de ser distribuidos. En total, durante la existencia del sistema, teníamos alrededor de 46,000 parches para el maestro.

Source: https://habr.com/ru/post/es413991/


All Articles