Migración de Facebook a MySQL 8.0

MySQL

, una base de datos de código abierto desarrollada por Oracle, impulsa algunas de las cargas de trabajo más importantes de Facebook. Desarrollamos activamente nuevas funciones en MySQL para respaldar nuestros requisitos en constante evolución.

Estas características cambian muchas áreas diferentes de MySQL, incluidos los conectores del cliente, el motor de almacenamiento, el optimizador y la replicación.

Cada nueva versión principal de MySQL requiere mucho tiempo y esfuerzo para migrar nuestras cargas de trabajo.

Los desafíos incluyen:

  • Portando nuestras funciones personalizadas a la nueva versión
  • Garantizar que la replicación sea compatible entre las versiones principales
  • Minimizar los cambios necesarios para las consultas de aplicaciones existentes
  • Arreglar las regresiones de rendimiento que impiden que el servidor admita nuestras cargas de trabajo

Nuestra última actualización importante de la versión, a MySQL 5.6, tardó más de un año en implementarse. Cuando se lanzó la versión 5.7, todavía estábamos en medio del desarrollo de nuestro motor de almacenamiento LSM-Tree, MyRocks , en la versión 5.6. Dado que actualizar a 5.7 y al mismo tiempo construir un nuevo motor de almacenamiento habría ralentizado significativamente el progreso en MyRocks.

Optamos por permanecer con 5.6 hasta que MyRocks estuviera completo. MySQL 8.0 se anunció cuando estábamos terminando la implementación de MyRocks en nuestro nivel de servicio de base de datos de usuarios (UDB).

Esa versión incluía características atractivas como la replicación paralela basada en conjuntos de escritura y un diccionario de datos transaccionales que brindaba soporte DDL atómico.

Para nosotros, pasar a 8.0 también traerá las características de 5.7 que nos habíamos perdido, incluido Document Store.

La versión 5.6 se acercaba al final de su vida útil y queríamos mantenernos activos dentro de la comunidad MySQL, especialmente con nuestro trabajo en el motor de almacenamiento MyRocks.

Las mejoras en 8.0, como DDL instantáneo, podrían acelerar los cambios de esquema de MyRocks, pero necesitábamos estar en la base de código 8.0 para usarlo. Dados los beneficios de la actualización del código, decidimos migrar a 8.0.

Compartimos cómo abordamos nuestro proyecto de migración 8.0 y algunas de las sorpresas que descubrimos en el proceso. Cuando analizamos inicialmente el proyecto, estaba claro que pasar a 8.0 sería incluso más difícil que migrar a 5.6 o MyRocks.

  • En ese momento, nuestra rama 5.6 personalizada tenía más de 1.700 parches de código para migrar a 8.0. Mientras portábamos esos cambios, se agregaron nuevas funciones y correcciones de Facebook MySQL a la base de código 5.6 que movió el poste de la portería más lejos.
  • Tenemos muchos servidores MySQL en producción, que sirven a una gran cantidad de aplicaciones dispares. También contamos con una amplia infraestructura de software para administrar instancias de MySQL. Estas aplicaciones realizan operaciones como recopilar estadísticas y administrar copias de seguridad del servidor.
  • La actualización de 5.6 a 8.0 omitió 5.7 por completo. Las API que estaban activas en 5.6 habrían quedado obsoletas en 5.7 y posiblemente eliminadas en 8.0, lo que nos obliga a actualizar cualquier aplicación que utilice las API ahora eliminadas.
  • Varias funciones de Facebook no eran compatibles con otras similares en 8.0 y requerían una ruta de desaprobación y migración hacia adelante.
  • Se necesitaban mejoras de MyRocks para ejecutarse en 8.0, incluida la partición nativa y la recuperación de fallos.

Parches de código

Primero configuramos la rama 8.0 para compilar y probar en nuestros entornos de desarrollo. Luego comenzamos el largo viaje para trasladar los parches desde nuestra sucursal 5.6.

Había más de 1.700 parches cuando comenzamos, pero pudimos organizarlos en algunas categorías principales.

La mayor parte de nuestro código personalizado tenía buenos comentarios y descripciones, por lo que pudimos determinar fácilmente si las aplicaciones aún lo necesitaban o si podía descartarse.

Las características habilitadas por palabras clave especiales o nombres de variables únicos también facilitaron la determinación de la relevancia porque pudimos buscar en nuestras bases de código de aplicaciones para encontrar sus casos de uso.

Algunos parches eran muy oscuros y requerían un trabajo de detective (investigar documentos de diseño antiguos, publicaciones y / o comentarios de revisión de código) para comprender su historial.

Clasificamos cada parche en uno de cuatro grupos:

  1. Eliminar: las funciones que ya no se usaban, o que tenían una funcionalidad equivalente en 8.0, no necesitaban ser transferidas.
  2. Compilación / Cliente: se transfirieron funciones que no eran de servidor que admitían nuestro entorno de compilación y herramientas MySQL modificadas como mysqlbinlog, o funciones agregadas como la API de cliente asíncrono.
  3. Servidor no MyRocks: se transfirieron características en el servidor mysqld que no estaban relacionadas con nuestro motor de almacenamiento MyRocks.
  4. Servidor MyRocks: se transfirieron las funciones que admitían el motor de almacenamiento MyRocks.

Hicimos un seguimiento del estado y la información histórica relevante de cada parche mediante hojas de cálculo y registramos nuestro razonamiento al lanzar un parche.

Se agruparon varios parches que actualizaron la misma función para la portabilidad. Los parches portados y comprometidos a la rama 8.0 se anotaron con la información de confirmación 5.6.

Las discrepancias en el estado de la migración surgirían inevitablemente debido a la gran cantidad de parches que necesitábamos examinar y estas notas nos ayudaron a resolverlos.

Cada una de las categorías de cliente y servidor se convirtió naturalmente en un hito en el lanzamiento de software. Con todos los cambios relacionados con el cliente transferidos, pudimos actualizar las herramientas de nuestro cliente y el código del conector a 8.0.

Una vez que se transfirieron todas las características del servidor que no eran de MyRocks, pudimos implementar 8.0 mysqld para servidores InnoDB.

La finalización de las funciones del servidor MyRocks nos permitió actualizar las instalaciones de MyRocks.

Algunas de las características más complejas requirieron cambios significativos para la versión 8.0 y algunas áreas tuvieron importantes problemas de compatibilidad.

Por ejemplo, los formatos de eventos binlog de upstream 8.0 eran incompatibles con algunas de nuestras modificaciones personalizadas de 5.6. Los códigos de error utilizados por las funciones de Facebook 5.6 entraron en conflicto con los asignados a las nuevas funciones por upstream 8.0.

En última instancia, necesitábamos parchear nuestro servidor 5.6 para que fuera compatible con la versión 8.0.

Tomó un par de años completar la portabilidad de todas estas características. Para cuando llegamos al final, habíamos evaluado más de 2,300 parches y trasladamos 1,500 de ellos a 8.0.

El camino de la migración

Agrupamos varias instancias de mysqld en un solo conjunto de réplicas de MySQL. Cada instancia de un conjunto de réplicas contiene los mismos datos, pero se distribuye geográficamente a un centro de datos diferente para proporcionar disponibilidad de datos y compatibilidad con la conmutación por error.

Cada conjunto de réplicas tiene una instancia principal. Las instancias restantes son todas secundarias. El primario maneja todo el tráfico de escritura y replica los datos de forma asincrónica a todos los secundarios.

Comenzamos con conjuntos de réplicas que constan de 5.6 primarios / 5.6 secundarios y el objetivo final era conjuntos de réplicas con 8.0 primarios / 8.0 secundarios.

Seguimos un plan similar al plan de migración de UDB MyRocks .

  1. Para cada conjunto de réplicas, cree y agregue 8.0 secundarios a través de una copia lógica usando mysqldump. Estos secundarios no sirven a ningún tráfico de lectura de aplicaciones.
  2. Habilite el tráfico de lectura en los secundarios 8.0.
  3. Permita que la instancia 8.0 se promueva a primaria.
  4. Deshabilite las instancias 5.6 para el tráfico de lectura.
  5. Elimine todas las instancias 5.6.

Cada conjunto de réplicas podría realizar la transición a través de cada uno de los pasos anteriores de forma independiente y permanecer en un paso todo el tiempo que sea necesario.

Separamos los conjuntos de réplicas en grupos mucho más pequeños, que guiamos a través de cada transición. Si encontramos problemas, podríamos retroceder al paso anterior.

En algunos casos, los conjuntos de réplicas pudieron llegar al último paso antes de que comenzaran otros.

Para automatizar la transición de una gran cantidad de conjuntos de réplicas, necesitábamos crear una nueva infraestructura de software.

Podríamos agrupar conjuntos de réplicas y moverlos a través de cada etapa simplemente cambiando una línea en un archivo de configuración. Cualquier conjunto de réplicas que encontrara problemas podría revertirse individualmente.

Replicación basada en filas

Como parte del esfuerzo de migración 8.0, decidimos estandarizar el uso de la replicación basada en filas (RBR). Algunas características de la versión 8.0 requerían RBR y simplificó nuestros esfuerzos de migración de MyRocks.

Si bien la mayoría de nuestros conjuntos de réplicas de MySQL ya usaban RBR, los que aún ejecutaban la replicación basada en declaraciones (SBR) no se podían convertir fácilmente.

Estos conjuntos de réplicas generalmente tenían tablas sin claves de cardinalidad alta. Cambiar por completo a RBR había sido un objetivo, pero la larga cola de trabajo necesaria para agregar claves primarias a menudo se priorizaba por debajo de otros proyectos.

Por lo tanto, hicimos que RBR fuera un requisito para 8.0. Después de evaluar y agregar claves primarias a cada tabla, cambiamos el último conjunto de réplicas de SBR de este año.

El uso de RBR también nos brindó una solución alternativa para resolver un problema de aplicación que encontramos cuando movimos algunos conjuntos de réplicas a los primarios 8.0, que se discutirán más adelante.

Validación de automatización

La mayor parte del proceso de migración 8.0 involucró probar y verificar el servidor mysqld con nuestra infraestructura de automatización y consultas de aplicaciones.

A medida que nuestra flota de MySQL creció, también lo hizo la infraestructura de automatización que usamos para administrar los servidores.

Para garantizar que toda nuestra automatización de MySQL fuera compatible con la versión 8.0, invertimos en la creación de un entorno de prueba, que aprovechó los conjuntos de réplicas de prueba con máquinas virtuales para verificar los comportamientos.

Escribimos pruebas de integración para que cada pieza de automatización se ejecute tanto en la versión 5.6 como en la versión 8.0 y verificamos su corrección. Encontramos varios errores y diferencias de comportamiento a medida que realizamos este ejercicio.

A medida que cada pieza de la infraestructura MySQL se validó con nuestro servidor 8.0, encontramos y solucionamos (o solucionamos) una serie de problemas interesantes:

  1. El software que analizaba la salida de texto del registro de errores, la salida de mysqldump o los comandos de demostración del servidor se rompía fácilmente. Los cambios leves en la salida del servidor a menudo revelaron errores en la lógica de análisis de una herramienta.
  2. La configuración de intercalación de utf8mb4 predeterminada de 8.0 dio como resultado discrepancias de intercalación entre nuestras instancias 5.6 y 8.0. Las tablas 8.0 pueden usar las nuevas intercalaciones utf8mb4_0900 incluso para las declaraciones de creación generadas por la tabla show create de 5.6 porque los esquemas 5.6 que usan utf8mb4_general_ci no especifican explícitamente la intercalación. Estas diferencias en las tablas a menudo causaban problemas con las herramientas de verificación de esquemas y replicación.
  3. Los códigos de error para ciertas fallas de replicación cambiaron y tuvimos que arreglar nuestra automatización para manejarlos correctamente.
  4. El diccionario de datos de la versión 8.0 dejó obsoletos los archivos .frm de la tabla, pero parte de nuestra automatización los usó para detectar modificaciones en el esquema de la tabla.
  5. Tuvimos que actualizar nuestra automatización para admitir los privilegios dinámicos introducidos en 8.0.

Validación de la aplicación

Queríamos que la transición de las aplicaciones fuera lo más transparente posible, pero algunas consultas de aplicaciones tienen regresiones de rendimiento o fallarían en la versión 8.0.

Para la migración de MyRocks, creamos un marco de prueba de sombra de MySQL que capturaba el tráfico de producción y lo reproducía para probar las instancias.

Para cada carga de trabajo de la aplicación, construimos instancias de prueba en 8.0 y les reproducimos consultas de tráfico oculto. Capturamos y registramos los errores que regresaban del servidor 8.0 y encontramos algunos problemas interesantes.

Desafortunadamente, no todos estos problemas se encontraron durante las pruebas. Por ejemplo, las aplicaciones descubrieron el bloqueo de las transacciones durante la migración.

Pudimos revertir estas aplicaciones a 5.6 temporalmente mientras investigábamos diferentes soluciones.

  • Se introdujeron nuevas palabras clave reservadas en la versión 8.0 y algunas, como grupos y rango, entraron en conflicto con los nombres de columnas de la tabla y los alias más populares utilizados en las consultas de aplicaciones. Estas consultas no escaparon de los nombres a través de comillas inversas, lo que provocó errores de análisis. Las aplicaciones que usaban bibliotecas de software que escapaban automáticamente de los nombres de las columnas en las consultas no resolvieron estos problemas, pero no todas las aplicaciones las usaron. Solucionar el problema fue simple, pero tomó tiempo localizar a los propietarios de las aplicaciones y las bases de código que generaban estas consultas.
  • También se encontraron algunas incompatibilidades REGEXP entre 5.6 y 8.0.
  • Algunas aplicaciones alcanzan puntos muertos de transacciones de lectura repetible que implican insertar … en consultas de claves duplicadas en InnoDB. 5.6 tenía un error que se corrigió en 8.0, pero la solución aumentó la probabilidad de bloqueos de transacciones. Después de analizar nuestras consultas, pudimos resolverlas reduciendo el nivel de aislamiento. Esta opción estaba disponible para nosotros desde que hicimos el cambio a la replicación basada en filas.
  • Nuestras funciones personalizadas de almacenamiento de documentos 5.6 y JSON no eran compatibles con 8.0. Las aplicaciones que utilizan Document Store necesitaban convertir el tipo de documento en texto para la migración. Para las funciones JSON, agregamos versiones compatibles con 5.6 al servidor 8.0 para que las aplicaciones puedan migrar a la API 8.0 en un momento posterior.

Nuestra consulta y las pruebas de rendimiento del servidor 8.0 revelaron algunos problemas que debían abordarse casi de inmediato.

  • Encontramos nuevos puntos calientes de contención de mutex alrededor de la caché de ACL. Cuando se abren simultáneamente una gran cantidad de conexiones, todas pueden bloquearse al verificar las ACL.
  • Se encontró una contención similar con el acceso al índice binlog cuando hay muchos archivos binlog presentes y las altas tasas de escritura binlog rotan los archivos con frecuencia.
  • Varias consultas relacionadas con tablas temporales se rompieron. Las consultas devolverían errores inesperados o tardarían tanto en ejecutarse que se agotarían.

El uso de memoria en comparación con 5.6 había aumentado, especialmente para nuestras instancias MyRocks, porque se debe cargar InnoDB en 8.0.

La configuración predeterminada de performance_schema habilitó todos los instrumentos y consumió una cantidad significativa de memoria.

Limitamos el uso de memoria habilitando solo una pequeña cantidad de instrumentos y realizando cambios de código para deshabilitar tablas que no se podían apagar manualmente.

Sin embargo, performance_schema no asignaba toda la memoria aumentada.

Necesitábamos examinar y modificar varias estructuras de datos internas de InnoDB para reducir aún más la huella de memoria. Este esfuerzo redujo el uso de la memoria de 8.0 a niveles aceptables.

Fuente: Blog de Facebook

Relacionados