Actualizaciones MySQL vs MariaDB: cómo actualizar sin arruinar la producción

¿Te fue útil?

Si alguna vez actualizaste una base de datos “rápidamente” y luego viste tu aplicación entrar en un pánico silencioso y educado—latencias al alza, errores por todas partes, réplicas quedándose atrás—ya conoces la moraleja: las actualizaciones no fallan de forma estruendosa; fallan de formas creativas.

Esto es una guía para producción. No teoría. No marketing de proveedor. El objetivo es simple: avanzar MySQL o MariaDB (o moverse entre ellos) manteniendo tus datos seguros, la latencia razonable y la posibilidad de rollback creíble.

Elige tu camino: in-place, rolling o migración

La mayoría de las actualizaciones de base de datos fallan por una de tres razones: elegiste el método de actualización equivocado, te saltaste el ensayo o te mentiste a ti mismo sobre el rollback.

Opción A: Actualización mayor in-place (más rápida, más arriesgada)

In-place significa que detienes MySQL/MariaDB en un host y lo reinicias con la nueva versión apuntando al mismo datadir. Esto puede estar bien para sistemas pequeños y no críticos. En producción, suele ser el último recurso porque:

  • El rollback a menudo no es rollback. Una vez que los archivos de datos se actualizan, volver atrás puede ser “restaurar desde backup y rezar”.
  • El tiempo de inactividad es obligatorio.
  • Si descubres una regresión de rendimiento, depuras bajo presión de tiempo.

Si debes hacer in-place, hazlo primero en una réplica. Si no tienes réplicas, tu primer “paso de actualización” es: construir replicación.

Opción B: Upgrade rolling usando replicación (recomendado para la mayoría)

Esta es la forma madura. Actualizas réplicas primero, verificas y después haces el failover. Tu downtime se convierte en una ventana controlada de failover, no en un evento existencial.

  • Mejor para: MySQL async replication, semi-sync, MariaDB replication, muchos escenarios Galera (con restricciones de versión).
  • El rollback es creíble: volver al primario antiguo (si lo mantuviste intacto y la replicación sigue siendo compatible).
  • El riesgo se traslada de “formato de archivo de datos” a “compatibilidad de replicación y comportamiento de consultas.” Ese es un tipo de riesgo mejor.

Opción C: Migración vía dump lógico/restore o CDC (la más lenta, la más limpia)

Si estás cambiando de motor (MySQL → MariaDB o MariaDB → MySQL), o necesitas saltar a través de versiones mayores incompatibles, puedes hacer:

  • Lógico: mysqldump / mydumper, restaurar en el nuevo cluster, cut over.
  • CDC: replicación basada en binlog (nativa), o una canalización de change data capture que haga stream de los cambios al nuevo cluster.

La migración lógica es lenta pero determinista. CDC es elegante pero operativamente más pesada. En cualquier caso, obtienes un inicio limpio y menos sorpresas de formato de archivos.

Regla opinada: si el negocio importa, haz rolling upgrades o migración CDC. Las actualizaciones in-place en producción son la forma de aprender el verdadero significado de “mantenimiento no planificado”.

Algunos hechos e historia (las partes que aún muerden)

Las actualizaciones son más fáciles cuando sabes por qué el ecosistema luce como luce. Aquí hay hechos concretos que siguen importando operativamente:

  1. MariaDB se bifurcó de MySQL en 2009 tras la adquisición de Sun por Oracle. Esa división creó dos hojas de ruta divergentes con “mayormente compatibles” como un objetivo móvil.
  2. MySQL 5.7 → 8.0 no es una actualización mayor “normal”. El optimizador, el diccionario de datos, los valores por defecto y la autenticación cambiaron de formas que aparecen como errores en la aplicación, no solo tareas de DBA.
  3. MySQL 8.0 movió metadata a un data dictionary transaccional (basado en InnoDB). Operativamente: menos rarezas de la era .frm, pero las actualizaciones reescriben estructuras internas y pueden llevar tiempo real.
  4. MariaDB mantuvo Aria como motor de tablas del sistema en algunos lugares y desarrolló funciones de manera independiente (por ejemplo, comportamiento distinto con JSON y características del optimizador). Esa independencia es potencia—y riesgo de compatibilidad.
  5. “JSON” es un gran ejemplo de divergencia: el JSON de MySQL es un formato binario con funciones e índices optimizados; MariaDB históricamente trató JSON como un alias TEXT con funciones JSON que evolucionaron de forma distinta.
  6. Los plugins de autenticación cambiaron expectativas en MySQL 8.0 (caching_sha2_password por defecto). Clientes y proxies que “iban bien” durante años de repente no lo hacen.
  7. Las implementaciones de GTID difieren entre MySQL y MariaDB. No puedes asumir que simplemente “activar GTID” permite moverte entre ellos sin planificación.
  8. MySQL eliminó el query cache hace años (8.0), mientras que MariaDB lo mantuvo por más tiempo. Si dependías del query cache (incluso accidentalmente), las actualizaciones se sentirán como un “misterio” de rendimiento.
  9. Tamaño de página InnoDB, ajustes de redo log y comportamiento de recovery pueden cambiar valores por defecto y heurísticas entre versiones. Tu RTO podría moverse sin pedir permiso.

Chiste #1: El único “truco raro” en las actualizaciones de bases de datos es hacer un backup que realmente hayas restaurado al menos una vez.

Mapa de compatibilidad: qué se rompe entre MySQL y MariaDB

“Compatible” no es una propiedad binaria. Es una lista de cosas que eventualmente te harán daño en producción, con una marca temporal adjunta.

MySQL → MySQL (misma familia, aún peligroso)

  • SQL mode y valores por defecto: Cambios de comportamiento pueden aparecer como errores por truncamiento de datos o inserts “repentinamente estrictos”.
  • Regresiones del optimizador: La misma consulta puede elegir un nuevo plan y volverse lenta bajo carga.
  • Autenticación y TLS: Bibliotecas cliente, HAProxy/ProxySQL y versiones antiguas de JDBC pueden fallar al conectarse.
  • Filtrado de replicación y metadata: Diferencias menores de configuración pueden romper replicación en el corte.

MariaDB → MariaDB

  • Restricciones de versión de Galera: No puedes simplemente desplegar cualquier versión mayor en un cluster. Revisa rutas de upgrade rolling soportadas; si no, estás planificando una reconstrucción del cluster.
  • Cambios en tablas del sistema: mysql.* y esquemas de privilegios evolucionan; las actualizaciones pueden requerir pasos posteriores explícitos.
  • Historia InnoDB vs XtraDB: Versiones antiguas de MariaDB tenían caminos de código de storage engine distintos; las versiones modernas están más cerca, pero aún existen entornos legados.

MySQL ↔ MariaDB (cambio de motor)

Aquí es donde “arranca bien” se vuelve una trampa.

  • El formato de archivos de datos y las tablas del sistema difieren: intercambiar binarios in-place no es una estrategia; es una apuesta.
  • Diferencias de GTID: migrar setups de replicación entre ellos requiere diseño cuidadoso (y a menudo re-sembrado).
  • Las características SQL divergen: soporte de funciones de ventana, funciones JSON, hints del optimizador y palabras reservadas se separan.
  • Expectativas del cliente: comportamiento de conectores, defaults de autenticación y versiones de TLS varían entre distribuciones.

Regla opinada: trata MySQL ↔ MariaDB como una migración, no como una “actualización”. Planifica ejecución dual, validación y cutover con una ventana de rollback.

Guion rápido de diagnóstico: qué comprobar primero/segundo/tercero

Cuando una actualización se sale de cauce, no necesitas 40 pestañas en el navegador. Necesitas una secuencia de triaje que te diga dónde vive el tiempo y el dolor.

Primero: ¿es CPU, IO o bloqueos?

  • CPU-bound: regresión del plan, índices faltantes, aumento de sort/temporary tables, o sobrecarga TLS/autenticación bajo muchas reconexiones.
  • IO-bound: cambio en comportamiento de flushing, presión de doublewrite, cambios en redo logging, buffer pool demasiado pequeño, salto de latencia de almacenamiento.
  • Lock-bound: metadata locks, nuevo comportamiento de DDL, transacciones largas o threads de apply de replicación asfixiados.

Segundo: ¿la replicación está sana y comparable?

  • El lag de réplicas y errores de apply tras la actualización son advertencias tempranas. A menudo se ignoran hasta el corte. No lo hagas.
  • Confirma formato de binlog, modo GTID (si se usa) y estado del SQL thread de la réplica.

Tercero: ¿la app cambió su comportamiento?

  • Thrash del pool de conexiones por mismatch del plugin de auth y bucles de reconnect.
  • Nueva estricticidad causando reintentos.
  • Zonas horarias, collation o conjunto de caracteres cambiados que producen diferencias sutiles y pérdidas de caché.

Cuarto: ¿la observabilidad sobrevivió la actualización?

  • Los settings de Performance Schema, formato del slow log y nombres de métricas pueden cambiar. Si los dashboards quedaron en negro, depuras a ciegas.

Mejor movimiento bajo presión: compara una consulta costosa conocida en viejo vs nuevo usando los mismos datos y parámetros. Si el plan difiere, ya tienes una dirección.

Chiste #2: “No cambiamos nada” es el equivalente en bases de datos a “el perro se comió mi tarea”, excepto que el perro es un optimizador.

Tareas en producción: comandos, salidas y decisiones (12+)

Estas son tareas que puedes ejecutar en servidores reales. Cada una incluye: comando, un fragmento de salida plausible, qué significa y la decisión que tomas.

Tarea 1: Identificar versión exacta del servidor y distribución

cr0x@server:~$ mysql -e "SELECT VERSION() AS version, @@version_comment AS distro, @@version_compile_machine AS arch\G"
*************************** 1. row ***************************
version: 8.0.36
distro: MySQL Community Server - GPL
arch: x86_64

Significado: No estás adivinando. “MySQL 8” no es una versión; 8.0.36 sí lo es. MariaDB mostrará comentarios distintos.

Decisión: Fija objetivos de actualización explícitos. Si no puedes nombrar tus versiones actuales y de destino, no estás planeando una actualización; estás planeando una sorpresa.

Tarea 2: Comprobar diffs críticos de configuración (runtime efectivo)

cr0x@server:~$ mysql -e "SHOW VARIABLES WHERE Variable_name IN ('datadir','log_bin','binlog_format','gtid_mode','enforce_gtid_consistency','innodb_buffer_pool_size','innodb_flush_log_at_trx_commit','sync_binlog','sql_mode','character_set_server','collation_server')"
+------------------------------+---------------------------+
| Variable_name                | Value                     |
+------------------------------+---------------------------+
| binlog_format                | ROW                       |
| character_set_server         | utf8mb4                   |
| collation_server             | utf8mb4_0900_ai_ci        |
| datadir                      | /var/lib/mysql/           |
| enforce_gtid_consistency     | ON                        |
| gtid_mode                    | ON                        |
| innodb_buffer_pool_size      | 34359738368               |
| innodb_flush_log_at_trx_commit| 1                        |
| log_bin                      | ON                        |
| sql_mode                     | ONLY_FULL_GROUP_BY,...    |
| sync_binlog                  | 1                         |
+------------------------------+---------------------------+

Significado: Esta es tu personalidad operativa: durabilidad, formato de replicación, estricticidad y manejo de caracteres.

Decisión: Cualquier upgrade/migración debe preservar la intención. Si la nueva versión cambia valores por defecto, sobreescríbelos explícitamente en la configuración, no en tus esperanzas.

Tarea 3: Confirmar espacio libre en disco antes de la actualización

cr0x@server:~$ df -h /var/lib/mysql
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p2  900G  720G  135G  85% /var

Significado: Las actualizaciones mayores pueden crear archivos temporales, reconstruir estructuras internas y expandir redo/undo durante el catch-up.

Decisión: Menos de ~20% libre es una señal de alarma. Expande almacenamiento o planea una migración a un volumen nuevo. “Estaremos bien” no es una estrategia de sistema de archivos.

Tarea 4: Buscar tablas corruptas o advertencias InnoDB

cr0x@server:~$ sudo tail -n 60 /var/log/mysql/error.log
2025-12-29T02:14:11.102334Z 0 [Warning] [MY-010068] [Server] CA certificate ca.pem is self signed.
2025-12-29T02:15:03.981245Z 0 [Warning] [MY-012639] [InnoDB] Retry attempts for reading partial data failed.
2025-12-29T02:15:04.001103Z 0 [Note] [MY-010431] [Server] Detected data dictionary initialization

Significado: Las actualizaciones amplifican daños existentes. Si InnoDB ya está quejándose, no apiles riesgo encima.

Decisión: Investiga y remedia problemas de almacenamiento/log (SMART/NVMe, errores de FS, mensajes del kernel). Si es necesario, toma un backup lógico fresco.

Tarea 5: Medir salud de replicación en réplicas (antes de tocar nada)

cr0x@server:~$ mysql -e "SHOW REPLICA STATUS\G" | egrep "Replica_IO_Running|Replica_SQL_Running|Seconds_Behind_Source|Last_SQL_Error|Retrieved_Gtid_Set|Executed_Gtid_Set"
Replica_IO_Running: Yes
Replica_SQL_Running: Yes
Seconds_Behind_Source: 0
Last_SQL_Error:
Retrieved_Gtid_Set: 2f9c3b3a-...:1-98233411
Executed_Gtid_Set: 2f9c3b3a-...:1-98233411

Significado: Una réplica que no puede replicar hoy no va a replicar mágicamente después de que la actualices.

Decisión: Arregla la deriva de replicación primero. No actualices encima de replicación rota; perderás la vía de rollback más fácil.

Tarea 6: Buscar transacciones largas (asesinas de upgrade y failover)

cr0x@server:~$ mysql -e "SELECT trx_id, trx_started, TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) AS age_s, trx_mysql_thread_id, trx_query FROM information_schema.innodb_trx ORDER BY age_s DESC LIMIT 5\G"
*************************** 1. row ***************************
trx_id: 145922993
trx_started: 2025-12-29 01:47:12
age_s: 2081
trx_mysql_thread_id: 17322
trx_query: UPDATE orders SET status='PAID' WHERE id=...

Significado: Las transacciones largas bloquean purge, pueden bloquear DDL y hacer que los failovers tomen una eternidad (y las réplicas queden atrás).

Decisión: Antes del cutover, aplica una ventana de “no transacciones largas”: pausa jobs por lotes, arregla escritores atascados y considera fijar un max execution time cuando corresponda.

Tarea 7: Encontrar principales esperas (locks vs IO vs CPU) usando Performance Schema

cr0x@server:~$ mysql -e "SELECT event_name, COUNT_STAR, ROUND(SUM_TIMER_WAIT/1000000000000,2) AS total_s FROM performance_schema.events_waits_summary_global_by_event_name WHERE SUM_TIMER_WAIT > 0 ORDER BY SUM_TIMER_WAIT DESC LIMIT 8"
+------------------------------------------+------------+---------+
| event_name                               | COUNT_STAR | total_s |
+------------------------------------------+------------+---------+
| wait/io/table/sql/handler                | 192233331  | 8421.33 |
| wait/synch/mutex/innodb/buf_pool_mutex   |  98223311  | 3120.10 |
| wait/io/file/innodb/innodb_data_file     |  12233411  | 1777.54 |
| wait/lock/table/sql/handler              |   2233111  |  601.22 |
+------------------------------------------+------------+---------+

Significado: Estás viendo en qué pasa tiempo el servidor. No sentimientos. No Slack.

Decisión: Si dominan esperas de IO, planifica validación de almacenamiento y tuning de flush. Si dominan esperas de locks, audita consultas y el alcance de transacciones antes de actualizar.

Tarea 8: Comparar planes de consulta pre-upgrade con EXPLAIN ANALYZE

cr0x@server:~$ mysql -e "EXPLAIN ANALYZE SELECT * FROM sessions WHERE user_id=123 AND created_at > NOW() - INTERVAL 7 DAY ORDER BY created_at DESC LIMIT 50\G"
*************************** 1. row ***************************
EXPLAIN: -> Limit: 50 row(s)  (actual time=0.321..0.338 rows=50 loops=1)
    -> Index range scan on sessions using idx_user_created (user_id=123)  (actual time=0.320..0.333 rows=50 loops=1)

Significado: Esto es tu suero de la verdad. Si el “actual time” o el método de acceso cambia después de la actualización, encontraste tu regresión.

Decisión: Captura una base de consultas críticas y sus planes. Tras actualizar una réplica, compara. Si los planes divergen, arregla con índices, hints (con moderación) o reescrituras de consulta antes del cutover.

Tarea 9: Validar deriva de character set/collation que rompe índices y comparaciones

cr0x@server:~$ mysql -e "SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='app' AND DATA_TYPE IN ('varchar','text','char') AND COLLATION_NAME NOT LIKE 'utf8mb4%' LIMIT 10"
+--------------+------------+-------------+--------------------+-------------------+
| TABLE_SCHEMA | TABLE_NAME | COLUMN_NAME | CHARACTER_SET_NAME | COLLATION_NAME    |
+--------------+------------+-------------+--------------------+-------------------+
| app          | users      | email       | latin1             | latin1_swedish_ci |
+--------------+------------+-------------+--------------------+-------------------+

Significado: Collations mezcladas causan ordenamientos raros, expectativas de unicidad rotas y comparaciones lentas.

Decisión: No cambies collations durante un upgrade de motor a menos que estés listo para un proyecto de migración de esquema. Si debes hacerlo, hazlo como un cambio separado con su propio blast radius y plan de rollback.

Tarea 10: Comprobar características deprecadas/removidas usadas por tu carga

cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'query_cache%';"
Empty set (0.00 sec)

Significado: En MySQL 8.0, las variables del query cache desaparecieron. En MariaDB pueden existir según la versión.

Decisión: Si tu app se beneficiaba accidentalmente del query cache, verás carga extra después de la actualización. Prepara capacidad y arregla el caching en la capa de aplicación donde corresponde.

Tarea 11: Verificar compatibilidad de clientes con plugins de autenticación

cr0x@server:~$ mysql -e "SELECT user, host, plugin FROM mysql.user WHERE user IN ('app','replicator')\G"
*************************** 1. row ***************************
user: app
host: %
plugin: caching_sha2_password
*************************** 2. row ***************************
user: replicator
host: 10.0.0.%
plugin: mysql_native_password

Significado: Si la librería cliente no soporta caching_sha2_password, fallará al conectar o hará reconexiones en bucle y te hará un DoS cortés.

Decisión: O actualizas clientes/conectores/proxies antes del servidor, o fijas el plugin del usuario explícitamente. Prefiere actualizar clientes; degradar la autenticación es un trade-off de seguridad que debes tomar conscientemente.

Tarea 12: Confirmar que el binlog y la retención no explotarán durante la migración

cr0x@server:~$ mysql -e "SHOW BINARY LOGS;"
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| binlog.000331    |  10485776 |
| binlog.000332    | 1073741824|
| binlog.000333    | 1073741824|
+------------------+-----------+

Significado: Tus binlogs son grandes y frecuentes. Durante ensayos de cutover y reconstrucciones de réplicas, el churn de binlog puede llenar discos.

Decisión: Asegura headroom en disco y fija una expiración sensata de binlogs. Para MySQL 8.0, prefiere binlog_expire_logs_seconds. Evita ponerla “baja” durante upgrades a menos que te guste reconstruir réplicas.

Tarea 13: Tomar un backup lógico que realmente puedas restaurar (comprobación de restauración)

cr0x@server:~$ mysqldump --single-transaction --routines --events --triggers --hex-blob --set-gtid-purged=OFF --databases app > /backups/app.sql
cr0x@server:~$ mysql -e "CREATE DATABASE restore_test; USE restore_test; SOURCE /backups/app.sql;"
Query OK, 1 row affected (0.01 sec)

Significado: El backup existe y es utilizable. Eso es más raro de lo que debería ser.

Decisión: Si la restauración falla, detente. Arregla backup/restore antes de tocar binarios de producción. Los backups no son una casilla para marcar; son tu rollback cuando todo lo demás arde.

Tarea 14: Ejecutar en seco la compatibilidad de la aplicación con una instancia sombra

cr0x@server:~$ mysql -e "SET GLOBAL log_output='TABLE'; SET GLOBAL slow_query_log=ON; SET GLOBAL long_query_time=0.2;"
Query OK, 0 rows affected (0.00 sec)

Significado: Puedes capturar consultas lentas en la réplica actualizada mientras ejecutas tráfico de lectura espejado (o pruebas sintéticas).

Decisión: Si la réplica actualizada muestra nuevas consultas lentas para la misma carga, no hagas el cutover hasta entender por qué. “Probablemente está bien” es cómo se entra en un incidente de larga duración.

Tres mini-historias corporativas desde las trincheras

Incidente causado por una suposición errónea: “replicación es replicación”

Manejaban una plataforma SaaS ocupada con una replicación asíncrona ordenada: un primario, dos réplicas y backups nocturnos. El plan era “modernizar” moviéndose de MariaDB a MySQL porque una herramienta del proveedor “prefería MySQL 8.” El documento de migración tenía dos páginas. Eso debió haber sido una pista.

La suposición errónea fue sutil: asumieron que su enfoque de replicación con GTID en MariaDB mapearía limpio al GTID de MySQL. El equipo construyó una réplica MySQL nueva, intentó conectarla al primario MariaDB y descubrió que “GTID” no es un estándar único entre forks. Pivotaron a replicación por file/position, que en cierto modo funcionó hasta que una prueba de failover introdujo un mismatch de posición de binlog y la réplica empezó a rechazar eventos.

En la ventana de cutover, la aplicación cambió escrituras al nuevo primario MySQL, y el cluster viejo MariaDB debía permanecer como rollback. Pero el rollback necesitaba replicación inversa para mantenerse cercano. Ese camino inverso no fue estable. En una hora, tenían dos realidades divergentes y ninguna línea limpia de regreso.

La solución no fue glamurosa. Reconstruyeron la migración como una pipeline CDC con checks de consistencia explícitos, y dejaron de intentar que GTID significara lo mismo entre motores. También implementaron una regla estricta: el rollback debe probarse con tráfico de escritura real en un ensayo, no “parece plausible.” La plataforma sobrevivió. El documento de dos páginas no.

Una optimización que se volvió en contra: “ajustemos flushing durante la actualización”

Otra compañía tenía una flota MySQL 5.7 en NVMe rápidos. Durante un upgrade planificado a 8.0, alguien sugirió un tweak de rendimiento: bajar settings de durabilidad temporalmente para acelerar el catch-up y reducir tiempo de failover. Específicamente, redujeron innodb_flush_log_at_trx_commit y sync_binlog en las réplicas actualizadas.

En staging, se veía genial. La replicación aplicaba más rápido; las consultas eran ágiles. En producción, sin embargo, había un ingrediente extra: eventos ocasionales de pérdida de energía en un rack y un quirk del firmware del controlador de almacenamiento que había sido inofensivo bajo flush estricto. Dos días después del rollout, una réplica actualizada falló. El crash recovery se completó, pero reactivó logs de forma distinta a lo esperado y apareció una inconsistencia en una tabla de edge-case que usaba mezcla de patrones autocommit y un trigger legado.

No se “perdieron” datos en el sentido existencial—la mayoría de las tablas estaban bien—pero la inconsistencia fue suficiente para bloquear la promoción de esa réplica y forzar una reconstrucción durante el rollout. El upgrade general se ralentizó dramáticamente. La “optimización” añadió más riesgo de downtime del que eliminó.

La lección: no introduzcas cambios de tuning dentro de un upgrade a menos que estés listo para debuggear como si fueran features de producción. Si quieres cambiar perillas de durabilidad, hazlo como su propio cambio, con su propia ventana de pruebas, y acepta el trade-off explícitamente.

Una idea parafraseada a menudo atribuida a Werner Vogels: “Todo falla; diseña y opera asumiendo que fallará.” Eso incluye tus ajustes ingeniosos durante el upgrade.

Una práctica aburrida pero correcta que salvó el día: ensayo de cutover con rollback real

Un equipo empresarial tenía la costumbre que todos burlaban: hacían ensayos completos de dress rehearsals para actualizaciones de base de datos, incluyendo el rollback. No “tenemos backups”, sino “practicamos regresar bajo carga.” Parecía excesivo—hasta que no lo fue.

Estaban actualizando MariaDB en un setup de tres nodos usado por un sistema financiero interno. Durante el ensayo #1, encontraron que su herramienta de migración de esquema abría transacciones de larga duración, lo que causaba metadata locks durante el failover. Eso hubiera convertido un cutover de 2 minutos en un outage de 45 minutos. Ajustaron la configuración de la herramienta y separaron cambios de esquema del upgrade del motor.

Durante el ensayo #2, descubrieron que su agente de monitoreo usaba una variable de status deprecada y dejó de reportar lag de replicación en los nodos actualizados. Actualizaron dashboards y alertas antes de producción.

En la noche real, la actualización fue bien—hasta que un cambio de ACL de red en otro lado provocó pérdida intermitente de paquetes a una réplica. El equipo vio jitter en la replicación inmediatamente (porque el monitoreo aún funcionaba), pausaron la promoción y hicieron failover al otro réplica actualizado en su lugar. Sin incidente. El trabajo aburrido del ensayo hizo todo el trabajo dramático por ellos.

Listas de verificación / plan paso a paso

Hay dos proyectos de actualización escondidos en uno: el cambio del engine de base de datos y el cambio operativo. Trata ambos seriamente.

Fase 0: Decide qué vas a hacer realmente

  • Mismo motor, versión mayor (MySQL 5.7→8.0, MariaDB 10.x→11.x): usa rolling upgrade donde sea posible.
  • Cambio de motor (MySQL ↔ MariaDB): planifica migración con validación. Asume incompatibilidades hasta demostrarse lo contrario.
  • Galera: verifica rutas de upgrade rolling soportadas. Si no están soportadas, planifica construir un cluster nuevo y hacer cutover.

Fase 1: Checklist preflight (haz esto una semana antes)

  • Inventario: versiones, configs, plugins, uso de motores de almacenamiento.
  • Baseline: latencia p95/p99, consultas top, lag de replicación, hit rate del buffer pool, presión redo/undo.
  • Backups: prueba de restauración de backup lógico completo; prueba de restauración física si la usas.
  • Compatibilidad de clientes: conectores, proxies, TLS, plugins de autenticación.
  • Headroom de disco e IOPS: las actualizaciones causan picos.
  • Define el éxito: tasa de error aceptable, latencia y duración de cutover.

Fase 2: Construir un entorno de ensayo para el upgrade (no te lo saltes)

Usa datos parecidos a producción. Si no puedes, al menos usa el esquema de producción y un replay de workload.

  • Restaura el backup de la noche anterior en staging.
  • Adjunta un replay de workload solo-lectura o un benchmark sintético que aproxime la mezcla de consultas.
  • Ejecuta tests de integración de la aplicación contra la nueva versión.
  • Compara planes de consulta para consultas críticas.

Fase 3: Plan de rolling upgrade (basado en replicación)

Paso 1: Actualiza una réplica

  • Detén la replicación (o mantenla corriendo según el método), actualiza el binario, ejecuta los pasos post-upgrade requeridos.
  • Deja que se ponga al día, verifica velocidad de apply y latencia de consultas.

Paso 2: Validar profundamente antes de tocar el primario

  • Ejecuta tráfico de solo-lectura contra la réplica actualizada (o mirror de lecturas) y compara errores/latencia.
  • Confirma que la replicación es estable durante horas, no minutos.
  • Confirma que los backups funcionan en la nueva versión (las herramientas a veces cambian comportamiento).

Paso 3: Actualiza las réplicas restantes

Nunca actualices todas las réplicas a la vez si puedes evitarlo. Mantén al menos una réplica conocida en la versión antigua hasta que el cutover sea exitoso y estable.

Paso 4: Cutover

  • Quiesce las escrituras brevemente si tu proceso de failover lo requiere.
  • Promociona una réplica actualizada a primario.
  • Reapunta la aplicación, verifica rápidamente y luego reactiva gradualmente jobs en segundo plano.

Paso 5: Plan de rollback (lo practicas)

  • Mantén el primario antiguo intacto durante una ventana de rollback definida.
  • Decide condiciones disparadoras de rollback (latencia, tasa de error, inestabilidad de replicación).
  • Tener un procedimiento scriptado y probado para reapuntar tráfico de vuelta.

Fase 4: Plan de migración (MySQL ↔ MariaDB)

Si cambias de motor, prefiere construir un cluster nuevo y un cutover controlado:

  • Provisiona el nuevo cluster con engine/version de destino.
  • Carga datos base vía restore físico (si es compatible) o restore lógico.
  • Stream de cambios usando replicación/CDC (dependiente del engine).
  • Valida con checksums y lecturas sombra de la aplicación.
  • Cut over escrituras; mantén el cluster viejo en read-only durante la ventana de rollback.

Errores comunes: síntoma → causa raíz → solución

1) Síntoma: la actualización tiene éxito, pero la app no puede conectar

Causa raíz: mismatch de plugin de autenticación (ej., MySQL 8 caching_sha2_password) o mismatch de TLS/cipher en proxies/clientes.

Solución: actualiza conectores/proxies primero; verifica con un host canario. Si es emergencia, fija el plugin del usuario a mysql_native_password y programa la remediación adecuada.

2) Síntoma: pico repentino de latencia en consultas, mismo uso de CPU

Causa raíz: regresión del optimizador o cambio en comportamiento de estadísticas.

Solución: captura EXPLAIN ANALYZE en viejo vs nuevo; añade/ajusta índices; actualiza histogramas donde se usan; considera técnicas de estabilidad de plan (pero trata los hints como deuda técnica).

3) Síntoma: réplicas se quedan muy atrás solo después de la actualización

Causa raíz: la réplica actualizada aplica eventos row más lento por diferente comportamiento de fsync, diferentes settings de replicación paralela o límites de IO.

Solución: valida latencia de IO, incrementa paralelismo de réplica donde sea soportado, asegura que binlog_format y row image sean consistentes, y arregla saturación de almacenamiento antes del cutover.

4) Síntoma: failover tarda mucho, conexiones se amontonan

Causa raíz: transacciones largas / metadata locks / recovery lento en el candidato a primario.

Solución: identifica y detén transacciones largas antes del cutover; aplica reglas de ventana de mantenimiento para jobs por lotes; verifica tiempo de crash recovery mediante reinicio controlado en ensayo.

5) Síntoma: los datos se ven “diferentes” (orden, unicidad, comparaciones)

Causa raíz: diferencias en collation/character set, o cambios en comportamiento de funciones JSON/text entre motores.

Solución: fija character_set_server/collation_server explícitamente; audita columnas con collations mixtos; añade COLLATE explícito en consultas críticas si es necesario; evita cambiar collations durante el upgrade.

6) Síntoma: dashboards de monitoreo fallaron la noche del upgrade

Causa raíz: variables de status cambiadas, settings de Performance Schema o cambios en privilegios para el usuario de monitoreo.

Solución: prueba el agente de monitoreo contra la réplica actualizada durante el ensayo; actualiza consultas y grants; mantén un chequeo mínimo de “DB heartbeat” independiente de dashboards complejos.

7) Síntoma: disco se llena a mitad del upgrade

Causa raíz: crecimiento de binlog, desbordes de tablas temporales, expansión de redo logs durante el catch-up o artefactos de upgrade sobrantes.

Solución: asegura headroom; monitorea /var/lib/mysql y tmpdir; ubica tmpdir en un volumen dimensionado para los peores sorts; no reduzcas agresivamente la retención de binlogs durante migración.

8) Síntoma: consultas que antes “funcionaban” ahora fallan

Causa raíz: sql_mode más estricto, palabras reservadas o cambios en valores por defecto.

Solución: inventaría sql_mode y fíjalo; ejecuta suite de tests de la aplicación; busca en logs patrones de “deprecated” y “error near”; arregla consultas en lugar de relajar la corrección globalmente salvo que sea necesario.

FAQ

1) ¿Debería actualizar MySQL 5.7 a 8.0 in-place?

Sólo si el downtime es aceptable y tienes una ruta de restauración probada. Para cualquier cosa importante, actualiza réplicas primero y luego haz failover.

2) ¿Puedo cambiar de MariaDB a MySQL simplemente reemplazando binarios?

No. Trátalo como una migración. Los data dictionaries, tablas del sistema, GTIDs y comportamiento de características divergen. Construye un cluster nuevo y mueve datos con validación.

3) ¿Cuál es la estrategia de rollback más segura?

Mantén el primario antiguo intacto y escribible solo si estás haciendo un failback controlado con dirección de replicación conocida. Si no, déjalo en read-only y confía en restore/CDC. El rollback debe ensayarse.

4) ¿Cómo detecto regresiones del optimizador antes del cutover?

Elige 20–50 consultas críticas, captura EXPLAIN ANALYZE y estadísticas de ejecución en la versión antigua, y luego compara en una réplica actualizada con datos y carga similares a producción.

5) Mi app usa un conector antiguo. ¿Qué falla primero en MySQL 8?

Autenticación y TLS son puntos comunes de fallo: caching_sha2_password, expectativas de cifrados más estrictas y soporte de proxies. Actualiza clientes/proxies antes del servidor si puedes.

6) ¿Necesito ejecutar mysql_upgrade?

Depende de la versión y la distribución. Las versiones modernas de MySQL integran mucha lógica de upgrade, pero aún necesitas seguir los pasos post-recomendados por el proveedor. Regla operativa: verifica tablas del sistema y ejecuta el procedimiento de upgrade recomendado en una réplica primero.

7) ¿Y los clusters Galera—puedo hacer rolling upgrades?

A veces. Depende de las versiones exactas de MariaDB/Galera. Valida la ruta soportada y prueba en ensayo. Si no está soportado, construye un cluster nuevo y haz cutover.

8) ¿Debo cambiar valores por defecto de config durante el upgrade?

No a menos que tengas una razón específica y una prueba que lo respalde. Cambiar la versión del engine ya es una variable grande. Divide cambios de tuning en un proyecto separado a menos que te gusten causas ambiguas.

9) ¿Cuánto tiempo debo mantener el cluster viejo después del cutover?

El tiempo suficiente para cubrir tu ventana realista de “descubrimiento de bugs”—a menudo días, no horas—balanceado contra costo y riesgo operativo. Manténlo en un estado que soporte tu plan de rollback (read-only o listo para failback).

10) ¿Cuál es la forma más rápida de ganar confianza justo después del cutover?

Mira tres cosas: tasa de error de la app, latencia p95/p99 en algunos endpoints clave y salud de replicación (si tienes réplicas downstream). Si algo cambia, pausa y decide rápido.

Conclusión: pasos prácticos siguientes

Si no te llevas nada más: las actualizaciones no son un hobby de fin de semana. Son un cambio controlado al sistema más stateful que operaciones manejen.

  1. Elige el método: rolling upgrade vía replicación para upgrades mismo-motor; migración para switches MySQL ↔ MariaDB.
  2. Ensaya: restaura datos reales en staging, ejecuta workload, compara planes, prueba monitoreo.
  3. Haz real el rollback: define triggers, mantén el primario/cluster viejo listo para rollback y practica el procedimiento.
  4. Actualiza réplicas primero: valida durante horas bajo carga, no minutos.
  5. Haz el cutover con disciplina: quiesce jobs riesgosos, vigila error/latencia/replicación y está dispuesto a abortar.

El objetivo no es un comando de upgrade exitoso. El objetivo es un sistema de producción que se comporte de forma predecible la mañana siguiente.

← Anterior
Contenedores Docker con múltiples redes: evita la exposición accidental a la red equivocada
Siguiente →
Rendimiento de Ceph en Proxmox lento: 10 comprobaciones que realmente detectan el cuello de botella

Deja un comentario