A las 02:13 el pager no distingue qué logotipo tiene tu base de datos. Le importa que el proceso de checkout esté agotando el tiempo,
que las réplicas estén retrasadas y que alguien “acaba de aumentar max_connections” porque pareció útil.
MariaDB y Percona Server prometen rendimiento. A veces lo cumplen. A veces el “ajuste” es una funcionalidad que ya tenías,
una perilla que no deberías tocar o telemetría que es fantástica—hasta que se convierte en la carga. Separemos las victorias reales
del folleto comercial, y hagámoslo como realmente operamos en producción.
Lo que realmente estás eligiendo (no es un benchmark)
El debate MariaDB vs Percona Server suele plantearse como una carrera de aceleración: ¿cuál es más rápido? Esa es la pregunta equivocada.
En producción eliges un paquete de valores predeterminados, herramientas operativas, postura de compatibilidad y la forma del dolor que experimentarás
a escala.
Ambos, MariaDB y Percona Server, comenzaron como “MySQL, pero mejor.” Hoy son ecosistemas diferentes. Percona Server (para MySQL)
se mantiene cercano al comportamiento de MySQL upstream y sigue las versiones principales de MySQL. MariaDB tiene su propia línea de lanzamientos y se ha
desviado más con el tiempo—en ocasiones de formas realmente útiles, y en otras de maneras que sorprenden a equipos que asumieron “es
básicamente MySQL.”
Si ejecutas cargas OLTP ya razonablemente afinadas, las grandes ganancias de rendimiento rara vez provienen de la “marca del servidor.” Provienen de:
- higiene de consultas e índices (sí, todavía)
- dimensionamiento del buffer pool y control del churn de páginas
- comportamiento de redo/undo y ajustes de flush alineados con tu almacenamiento
- gestión de conexiones y planificación de hilos bajo cargas explosivas
- topología de replicación y elecciones de durabilidad que coincidan con el negocio
- observabilidad que no se convierte en la carga
La bifurcación que elijas importa más cuando necesitas una característica específica (thread pool, instrumentación, integración con herramientas de backup),
quieres reducir el riesgo operativo o necesitas preservar la compatibilidad con MySQL para aplicaciones de terceros. “Era un 12% más rápido en un benchmark de blog”
rara vez es una buena razón, porque el cuello de botella suele estar en otro sitio.
Hechos e historia interesante (versión corta y directa)
Un poco de contexto ayuda a decodificar el marketing:
- MariaDB se creó en 2009 tras la adquisición de Sun por parte de Oracle (y por tanto MySQL). El nombre es un guiño a la hija de Monty Widenius, al igual que “MySQL” lo fue a “My”.
- Percona construyó un negocio en apagar incendios de rendimiento MySQL antes de publicar Percona Server como una distribución con parches prácticos y herramientas.
- Percona Server históricamente incluía “XtraDB” (una variante de InnoDB enfocada en rendimiento) antes de que InnoDB de MySQL alcanzara muchas mejoras; la marca permanece, pero las mejoras upstream redujeron la brecha.
- Mariabackup existe porque XtraBackup de Percona se volvió el estándar de facto para backups físicos en caliente en el mundo MySQL; MariaDB luego ofreció un equivalente alineado a su base de código.
- El optimizador y el conjunto de características de MariaDB divergieron sustancialmente con los años; la compatibilidad con MySQL no es una promesa estática, es un blanco móvil.
- Performance Schema pasó de “sobrecarga opcional” a “realidad por defecto” en muchas distribuciones; la cuestión operativa se volvió “cómo muestrear con criterio”, no “si instrumentar”.
- La planificación por thread pool ha sido un tema recurrente porque el modelo conexión-por-hilo se comporta mal con tormentas de conexiones; el thread pooling es una solución práctica cuando las apps no se comportan.
- Los SSD modernos y NVMe cambiaron el playbook de tuning de InnoDB: los IOPS aleatorios se hicieron relativamente baratos, pero el comportamiento de fsync y la amplificación de escritura siguen mordiendo, especialmente con ajustes de durabilidad y doublewrite.
Afirmaciones de rendimiento: qué es real, qué es marketing, qué depende
1) “Mejor rendimiento por defecto”
A veces cierto, pero a menudo irrelevante. “Por defecto” depende de los valores del SO, el sistema de archivos, límites de cgroups, escalado de frecuencia de CPU
y si la distro envía configuraciones conservadoras.
Lo que puede ser real:
- Valores predeterminados más sensatos para producción (buffer pool, hilos de IO, método de flush) pueden marcar una diferencia notable para equipos que nunca afinan MySQL correctamente.
- Disponibilidad de thread pool (según edición/versión) puede estabilizar el rendimiento bajo conteos altos de conexiones.
- Controles extra de observabilidad pueden reducir los “unknown unknowns” en incidentes.
Qué es marketing:
- benchmarks que ocultan el verdadero cuello de botella (almacenamiento, red, agotamiento de pools de la app)
- afirmaciones que ignoran ajustes de durabilidad (rápido porque hace menos fsync)
- victorias de throughput en cliente único que no se traducen a latencia en la cola bajo concurrencia
2) “InnoDB / XtraDB mejorado significa escrituras más rápidas”
Históricamente, los parches XtraDB de Percona importaban más. Hoy, InnoDB upstream de MySQL ha incorporado muchas mejoras.
Las diferencias restantes tienden a estar en instrumentación, parámetros y comportamiento operativo en los extremos.
Aún existen ganancias reales, pero son condicionales:
- Cargas intensivas en escritura pueden beneficiarse de mejor comportamiento de flushing y dimensionamiento sensato del redo log—independientemente del fork.
- Cargas IO-bound se benefician más de sintonizar la capacidad de IO correctamente y evitar doble buffering que de la elección del fork.
- Entornos con mucha replicación valoran la eficiencia para diagnosticar, limitar y recuperar—las herramientas importan.
3) “El thread pool arregla la concurrencia”
Los thread pools son reales. Pueden transformar una tormenta de conexiones de “muerte del servidor” a “más lento pero vivo.” Pero no son
magia. Son control de admisión. No arreglan consultas lentas, índices malos o una sola fila caliente que todos actualizan.
El thread pool es más valioso cuando:
- tu aplicación abre demasiadas conexiones
- no puedes arreglar la app rápidamente (software de proveedor, muchos microservicios, pools heredados)
- tienes tráfico muy variable y necesitas proteger la latencia en la cola
4) “Mejor instrumentación”
El ecosistema de Percona históricamente enfatizó la visibilidad: análisis de consultas lentas, digests de consultas y valores predeterminados operativos más seguros.
MariaDB tiene su propia instrumentación y contadores de estado, y en algunos entornos es perfectamente adecuada.
La trampa es pensar que “más métricas” equivale a “más rendimiento.” La instrumentación puede añadir sobrecarga, especialmente el trazado a nivel de sentencia
o de espera bajo alto QPS. Actívala con intención. Muestrea. Rota. Automatiza.
Una idea parafraseada de Jim Gray (pionero en fiabilidad y procesamiento de transacciones): tratar las fallas como normales; diseñar para que el sistema permanezca correcto cuando ocurran.
Afinar rendimiento que compromete la corrección es solo una caída lenta.
5) “Reemplazo drop-in” y compatibilidad
Aquí es donde las dos bifurcaciones divergen en la práctica. Percona Server suele estar más cerca del comportamiento upstream de MySQL. MariaDB ha
introducido características y cambios que pueden sorprender a aplicaciones dependientes de MySQL—especialmente las que dependen de modos SQL específicos,
casos límite del optimizador, semántica de replicación o tablas del sistema.
Si ejecutas una aplicación de proveedor que indica “MySQL 8.0 soportado”, elegir MariaDB porque “es MySQL-ish” puede convertirse en una costosa prueba de personalidad.
Si controlas la app y pruebas adecuadamente, MariaDB puede ser una gran elección.
Broma #1: Los benchmarks son como los tableros en un coche de alquiler—puedes sentir que vas rápido mientras bajas por una cuesta con el motor apagado.
Guía de diagnóstico rápido (primero/segundo/tercero)
Cuando la latencia se dispara o el throughput se desploma, no empiezas cambiando el fork de la base de datos. Empiezas identificando el recurso limitante
y el tipo de espera dominante. Aquí está la ruta más corta que conozco que funciona bajo estrés.
Primero: ¿es CPU, IO, bloqueos o planificación de conexiones?
- Ligado a CPU: CPU de usuario alta, baja espera IO, consultas que queman ciclos, índices pobres, ordenaciones pesadas, joins malos.
- Ligado a IO: alta espera IO, latencia alta de fsync, churn de misses en buffer pool, retenciones por páginas sucias, presión de redo.
- Ligado a bloqueos: muchos hilos “Waiting for lock”, filas calientes, transacciones largas, bloqueos de metadata.
- Ligado a conexiones: demasiadas conexiones activas, cambios de contexto, colapso de planificación de hilos, backlog en la cola de accept.
Segundo: ¿proviene el problema de la carga principal o de “ayudantes”?
- hilos de replicación (SQL/aplicador) quedando atrás
- jobs de backup y snapshots golpeando el IO
- monitorización haciendo scrapes demasiado agresivos
- DDL u cambios de esquema online con throttling mal configurado
Tercero: ¿qué cambió?
- despliegues (cambio en la forma de las consultas)
- cambios de configuración (ajustes de flush, tamaños de buffers, concurrencia de hilos)
- crecimiento de datos (índice que ya no cabe en memoria)
- eventos de infraestructura (vecino ruidoso, firmware del almacenamiento extraño)
Tu objetivo en los primeros 10 minutos no es “arreglarlo para siempre.” Es clasificar el cuello de botella, detener la hemorragia y evitar
una falla en cascada (retardo de replicación, tormentas de reintentos, acumulación de conexiones).
Tareas prácticas que puedes ejecutar hoy (comandos, salidas, decisiones)
Esto no es teórico. Son los comandos que ejecutas cuando necesitas decidir si afinar InnoDB, matar una consulta, reducir la sobrecarga de monitorización
o dejar de culpar a la base de datos por un problema de almacenamiento.
Supuestos: host Linux, systemd y un cliente local. Ajusta los nombres de servicio si tu empaquetado difiere.
Task 1: Confirma qué estás ejecutando realmente
cr0x@server:~$ mysql -e "SELECT VERSION() AS version, @@version_comment AS comment, @@version_compile_machine AS arch;"
+-------------------------+------------------------------+------+
| version | comment | arch |
+-------------------------+------------------------------+------+
| 8.0.36-28 | Percona Server (GPL), Release 28 | x86_64 |
+-------------------------+------------------------------+------+
Qué significa: Ahora conoces la bifurcación y la línea de compilación exactas. Muchas guías de tuning son específicas por versión.
Decisión: Alinea tu tuning y expectativas de características con esta versión. No apliques perillas exclusivas de MariaDB a Percona, ni viceversa.
Task 2: Comprueba si estás limitado por CPU o por IO (vista rápida)
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 521240 81232 8123456 0 0 12 55 980 2100 22 6 69 3 0
6 3 0 498120 79012 8011200 0 0 820 2400 1250 9900 28 9 41 22 0
Qué significa: “wa” (espera IO) saltando al 22% sugiere presión de IO, no puramente CPU.
Decisión: Investiga flush/fsync, throttling de páginas sucias y latencia de almacenamiento antes de perseguir micro-optimizaciones de CPU por consulta.
Task 3: Mide la latencia de almacenamiento desde la vista del SO
cr0x@server:~$ iostat -x 1 3
avg-cpu: %user %nice %system %iowait %steal %idle
18.21 0.00 6.11 19.44 0.00 56.24
Device r/s w/s r_await w_await aqu-sz %util
nvme0n1 120.0 980.0 1.20 9.80 3.40 92.0
Qué significa: Las escrituras esperan ~10ms y la utilización es alta. Eso es un cuello de botella real si tu carga es intensiva en escritura.
Decisión: Revisa comportamiento de flush y redo en InnoDB, y verifica que no estés haciendo doble buffering (sistema de archivos + InnoDB) innecesariamente.
Task 4: Revisa hilos activos y principales esperas dentro de MySQL/MariaDB
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Threads_running'; SHOW GLOBAL STATUS LIKE 'Threads_connected';"
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| Threads_running | 184 |
+-----------------+-------+
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_connected | 620 |
+-------------------+-------+
Qué significa: 184 hilos en ejecución es mucho en la mayoría de máquinas. Si la CPU no está al tope, puede que estés limitado por bloqueos/IO o thrashing.
Decisión: Considera thread pool (si está disponible) o arreglar el pooling de la app. Mientras tanto, identifica los principales bloqueadores.
Task 5: Encuentra las peores consultas por tiempo (digest del slow log)
cr0x@server:~$ pt-query-digest /var/log/mysql/mysql-slow.log --limit=3
# 1 45% 120s 0.2s 600x SELECT orders ... WHERE user_id = ?
# 2 22% 60s 1.0s 60x UPDATE inventory ... WHERE sku = ?
# 3 15% 40s 0.1s 400x SELECT cart_items ... JOIN products ...
Qué significa: La consulta #1 no es lenta por ejecución, es lenta por volumen. La #2 es lenta por ejecución y probablemente genera contención.
Decisión: Arregla por impacto: reduce frecuencia primero (cache/modelo de lectura), luego arregla la lentitud por ejecución (índices, alcance de transacción).
Task 6: Confirma el dimensionamiento del buffer pool y un proxy de hit rate
cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';"
+-------------------------+------------+
| Variable_name | Value |
+-------------------------+------------+
| innodb_buffer_pool_size | 25769803776|
+-------------------------+------------+
+--------------------------+----------+
| Variable_name | Value |
+--------------------------+----------+
| Innodb_buffer_pool_reads | 18422012 |
+--------------------------+----------+
+----------------------------------+------------+
| Variable_name | Value |
+----------------------------------+------------+
| Innodb_buffer_pool_read_requests | 9871120031 |
+----------------------------------+------------+
Qué significa: Existen lecturas desde disco, pero necesitas una tasa a lo largo del tiempo para juzgar. Si las lecturas suben rápido durante picos, te falta caché.
Decisión: Si el working set no cabe, añade RAM, reduce el dataset o cambia los patrones de acceso (índices, archivado). La elección del fork no arregla la física.
Task 7: Revisa la presión del redo log y comportamiento de checkpoints
cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '1,120p'
Log sequence number 812345678901
Log flushed up to 812345670000
Last checkpoint at 812300000000
0 pending log flushes, 0 pending chkp writes
Qué significa: Si “Log flushed up to” va muy por detrás del LSN bajo carga, el fsync está atrasado. Si el checkpoint queda muy rezagado, puedes estar bloqueándote por páginas sucias.
Decisión: Ajusta el tamaño del redo log y los settings de flush con cautela; valida la latencia del almacenamiento. También revisa el porcentaje de páginas sucias y el flushing.
Task 8: Identifica esperas por bloqueos y sentencias bloqueantes
cr0x@server:~$ mysql -e "SHOW PROCESSLIST;" | head -n 15
Id User Host db Command Time State Info
91 app 10.0.2.5 shop Query 42 Waiting for table metadata lock ALTER TABLE orders ADD COLUMN ...
104 app 10.0.2.9 shop Query 41 Updating UPDATE inventory SET qty=qty-1 WHERE sku='X'
Qué significa: Un DDL está reteniendo o esperando locks de metadata y puede bloquear las consultas de la aplicación según el timing.
Decisión: Deja de ejecutar DDL sorpresa en primarios durante picos. Usa herramientas de cambio de esquema en línea y programa ventanas de mantenimiento con throttling consciente de la carga.
Task 9: Confirma la salud de la replicación (primario/replica)
cr0x@server:~$ mysql -e "SHOW REPLICA STATUS\G" | egrep 'Seconds_Behind_Source|Replica_IO_Running|Replica_SQL_Running|Last_SQL_Error'
Replica_IO_Running: Yes
Replica_SQL_Running: Yes
Seconds_Behind_Source: 187
Last_SQL_Error:
Qué significa: La réplica está sana pero con retraso. Eso es capacidad o eficiencia de apply, no una tubería rota.
Decisión: Revisa IO/CPU de la réplica, ajustes de replicación paralela y si transacciones largas en el primario están generando ráfagas de apply.
Task 10: Revisa tormentas de conexiones y abuso de max_connections
cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'max_connections'; SHOW GLOBAL STATUS LIKE 'Max_used_connections'; SHOW GLOBAL STATUS LIKE 'Aborted_connects';"
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| max_connections | 2000 |
+-----------------+-------+
+----------------------+-------+
| Variable_name | Value |
+----------------------+-------+
| Max_used_connections | 1987 |
+----------------------+-------+
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| Aborted_connects | 9321 |
+------------------+-------+
Qué significa: Estás llegando al límite. Aumentar max_connections suele hacer que el crash sea más ruidoso, no menos.
Decisión: Arregla el pooling y timeouts en la app. Considera thread pool. Aplica backpressure en el borde. Tratar 2000 conexiones como estrategia de escalado es un informe de bug.
Task 11: Verifica ajustes de durabilidad (y si “rendimiento” es solo menos fsync)
cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit'; SHOW VARIABLES LIKE 'sync_binlog';"
+------------------------------+-------+
| Variable_name | Value |
+------------------------------+-------+
| innodb_flush_log_at_trx_commit | 2 |
+------------------------------+-------+
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| sync_binlog | 0 |
+---------------+-------+
Qué significa: Esto es “más rápido” porque es menos duradero. Con caídas, puedes perder transacciones y eventos de binlog.
Decisión: Decide intencionalmente: si necesitas durabilidad fuerte, pon 1/1 (o un compromiso documentado) y compra el almacenamiento para soportarlo.
Task 12: Detecta presión por tablas temporales y ordenaciones (IO oculto en disco)
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables'; SHOW GLOBAL STATUS LIKE 'Created_tmp_tables'; SHOW GLOBAL STATUS LIKE 'Sort_merge_passes';"
+-------------------------+--------+
| Variable_name | Value |
+-------------------------+--------+
| Created_tmp_disk_tables | 120322 |
+-------------------------+--------+
+---------------------+--------+
| Variable_name | Value |
+---------------------+--------+
| Created_tmp_tables | 890441 |
+---------------------+--------+
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Sort_merge_passes | 9921 |
+-------------------+-------+
Qué significa: Muchas tablas temporales se derraman a disco y las ordenaciones están haciendo pases de merge. Eso es CPU e IO que no presupuestaste.
Decisión: Afina consultas e índices primero. Luego revisa tmp_table_size/max_heap_table_size y buffers de sort con cautela (buffers grandes × muchos hilos = explosión de RAM).
Task 13: ¿Sufres throttling por páginas sucias?
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_total';"
+--------------------------------+--------+
| Variable_name | Value |
+--------------------------------+--------+
| Innodb_buffer_pool_pages_dirty | 412000 |
+--------------------------------+--------+
+-------------------------------+--------+
| Variable_name | Value |
+-------------------------------+--------+
| Innodb_buffer_pool_pages_total| 1572864|
+-------------------------------+--------+
Qué significa: Aproximadamente 26% de páginas sucias. Si ves stalls y esto sube hacia el máximo configurado, puedes golpear tormentas de flushing.
Decisión: Revisa innodb_max_dirty_pages_pct y ajustes de capacidad IO. Si el almacenamiento no da abasto, afinar solo redistribuye el sufrimiento.
Task 14: Confirma binlog y GTID para saneamiento operativo
cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'gtid_mode'; SHOW VARIABLES LIKE 'enforce_gtid_consistency'; SHOW VARIABLES LIKE 'log_bin';"
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| gtid_mode | ON |
+--------------------------+-------+
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| enforce_gtid_consistency | ON |
+--------------------------+-------+
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin | ON |
+---------------+-------+
Qué significa: Tienes los prerrequisitos para flujos de failover de replicación saneados.
Decisión: Si te faltan, no persigas micro-optimizaciones—arregla la operabilidad primero, porque las outages cuestan más que un 5% de throughput.
Task 15: Comprueba si estás haciendo swap (asesino silencioso del rendimiento)
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 64Gi 58Gi 1.2Gi 1.0Gi 4.8Gi 2.0Gi
Swap: 8Gi 1.5Gi 6.5Gi
Qué significa: Se está usando swap. Bajo carga de base de datos, eso puede traducirse en picos de latencia aleatorios que parecen “MySQL lento”.
Decisión: Reduce la presión de memoria (buffers, conexiones, memoria por hilo) y considera deshabilitar swap solo si tienes disciplina fuerte de OOM y capacidad.
Task 16: Compara deltas de configuración clave entre dos servidores (sanidad de migración)
cr0x@server:~$ mysql -e "SHOW VARIABLES" | egrep '^(innodb_flush_method|innodb_io_capacity|innodb_log_file_size|max_connections|thread_handling)\s'
innodb_flush_method O_DIRECT
innodb_io_capacity 2000
innodb_log_file_size 1073741824
max_connections 2000
thread_handling one-thread-per-connection
Qué significa: Has sacado a la vista las perillas más propensas a cambiar características de rendimiento y modos de fallo entre hosts.
Decisión: Estandariza configuraciones cuando sea posible, luego prueba diferencias intencionalmente. Evita bases de datos “snowflake”.
Dónde fallan realmente los ajustes de rendimiento en producción
La elección del fork no te salvará de la matemática del IO
La matemática del IO es cruel y consistente. Si tienes una carga intensiva en escritura con commits durables (fsync en commit, sync del binlog), la
latencia del almacenamiento fija el piso de la latencia de commit. Un “fork más rápido” puede recortar la sobrecarga de CPU, pero no puede convertir un fsync de 10ms en 1ms.
La palanca real es alinear la durabilidad y el almacenamiento:
- NVMe con latencia de fsync predecible
- volúmenes separados (o al menos dominios de contención separados) para datos y logs cuando ayuda
- método de flush correcto para evitar doble cache
- dimensionado del redo log para reducir el thrash de checkpoints
El thread pool puede estabilizar, pero también ocultar la podredumbre
El thread pool es un cinturón de seguridad. Genial cuando el coche choca contra algo. No sustituye a aprender a conducir.
Con thread pooling, las consultas malas pueden volverse “menos obviamente malas” porque menos se ejecutan concurrentemente y el sistema deja de fundirse.
Eso es una victoria operativa, pero puede retrasar las correcciones necesarias de consultas. Tu backlog se convierte en una cola en vez de en un crash.
La instrumentación solo es buena cuando puedes permitírtela
Muchos incidentes de producción son “deuda de observabilidad” compuesta: nadie tenía slow logs habilitado, nadie mantenía digests de consultas, y ahora
todos quieren muestreo de sentencias completo durante el incidente. Encenderlo todo durante un fuego puede convertirse en el fuego.
Postura práctica:
- mantén el log de consultas lentas disponible y rotado
- habilita instrumentación de rendimiento a un nivel sostenible
- sabe cómo aumentar el muestreo temporalmente y revertirlo
Los problemas de compatibilidad a menudo aparecen como “problemas de rendimiento”
Cuando una app asume semánticas de MySQL y estás en MariaDB, puedes obtener cambios sutiles de plan, decisiones del optimizador distintas o
diferencias en variables de sistema. Eso aparece como “las consultas se pusieron lentas” después de una migración. No siempre es un problema de tuning;
a veces es una incompatibilidad semántica.
Broma #2: “Habilitamos todas las opciones de rendimiento” es el equivalente en bases de datos de “arreglamos el ruido quitando la alarma de humo.”
Tres mini-historias corporativas (anonimizadas, dolorosamente plausibles)
Mini-historia 1: El incidente causado por una suposición equivocada
Una compañía SaaS mediana ejecutaba una aplicación de proveedor “certificada para MySQL.” Estaban en una versión antigua de MySQL community y querían
mejor observabilidad y soporte. Alguien propuso MariaDB como reemplazo “drop-in”. Se instaló limpiamente, la replicación subió, los dashboards se veían bien y la migración
se declaró un éxito.
Dos semanas después, tras un pico de tráfico, la latencia se disparó en un endpoint específico que siempre había estado en el límite. El plan de consulta
cambió. No dramáticamente—lo suficiente para que se invirtiera el orden de join y un índice secundario no se usara igual. El equipo hizo lo que hacen los equipos: aumentaron
buffer pool, aumentaron tamaños de tablas temporales y añadieron CPU. Los picos se volvieron menos frecuentes, pero la cola empeoró.
El problema real fue la suposición: “si funciona, es compatible.” La matriz de soporte del proveedor indicaba la línea de versión mayor de MySQL. MariaDB no era “mala”;
era “diferente.” Con ciertas distribuciones de datos, la elección del optimizador hizo que la consulta regresara rendimiento.
La solución fue aburrida: restauraron compatibilidad con la familia MySQL moviéndose a Percona Server en la línea mayor soportada,
fijaron la consulta problemática con un índice más seguro y añadieron pruebas de regresión usando distribuciones de datos similares a producción.
La migración de regreso les costó una semana y muchas reuniones, pero recuperaron predictibilidad—que es la moneda real.
Mini-historia 2: La optimización que salió mal
Un equipo fintech quería menor latencia de commit. Tenían NVMe decente, pero sus escrituras p99 seguían siendo dolorosas en picos.
Un ingeniero bienintencionado cambió ajustes de durabilidad: puso innodb_flush_log_at_trx_commit=2 y
sync_binlog=0. Los benchmarks se veían geniales. Los gráficos mejoraron. Todos se fueron a casa.
Un mes después, un panic del kernel ocurrió durante una ventana de mantenimiento rutinaria. El primario volvió, las réplicas se reconectaron y la app funcionó en su mayoría—salvo que un subconjunto de transacciones desapareció.
No “rollbackeadas.” Desaparecidas. El binlog no las tenía, el redo log había reconocido commits sin persistir tan fuertemente como el negocio asumía, y la reconciliación fue un fin de semana de miseria controlada.
El postmortem fue honesto: la optimización no era “mala.” Era un riesgo no documentado. Habían cambiado el contrato del producto (durabilidad) sin avisar al negocio.
Su mejora de rendimiento fue real, y también la pérdida de datos.
La solución a largo plazo también fue aburrida: restaurar ajustes durables, actualizar almacenamiento para obtener latencias de fsync predecibles e introducir
un enfoque por niveles donde cargas no críticas usaran durabilidad relajada mientras que pagos no podían. El rendimiento vino de la arquitectura y el almacenamiento, no de
un ajuste deseado.
Mini-historia 3: La práctica aburrida pero correcta que salvó el día
Una compañía marketplace ejecutaba Percona Server con réplicas entre zonas. Nada sofisticado. Hicieron una cosa poco atractiva de forma consistente:
pruebas semanales de restauración desde backups físicos en un entorno de staging, más verificación periódica de que la instancia restaurada pudiera unirse a la replicación usando GTID.
Una tarde, un desarrollador desplegó una migración que creó un índice nuevo en una tabla masiva. El DDL usó un método online, pero la carga estaba alta y el throttling estaba mal afinado. El IO subió, las páginas sucias aumentaron y la replicación se retrasó. El primario sobrevivió, pero una réplica quedó tan atrás que empezó a fallar y luego corrompió su estado local tras reinicios repetidos.
El equipo no debatió por horas. Promovieron una réplica sana, aislaron la rota y la reconstruyeron desde el backup más reciente. El procedimiento de reconstrucción estaba documentado, ensayado y lo bastante automatizado como para funcionar bajo presión. El incidente se convirtió en una lección de rendimiento y no en un evento que acaba con la compañía.
Eso es lo importante: “los ajustes de rendimiento” son divertidos, pero la práctica operativa es lo que mantiene las luces encendidas cuando un ajuste sale mal.
Errores comunes (síntoma → causa raíz → solución)
1) Síntoma: p99 de latencia se dispara cada pocos minutos
Causa raíz: tormentas de checkpoints/flush (se acumulan páginas sucias y luego el flushing bloquea trabajo en primer plano), a menudo amplificadas por almacenamiento lento o mal ajuste de la capacidad IO.
Solución: verifica latencia de almacenamiento; ajusta innodb_io_capacity y innodb_io_capacity_max para que coincidan con la capacidad del dispositivo; redimensiona logs de redo; evita DDL pesados en pico.
2) Síntoma: throughput cae al aumentar tráfico, CPU no está al máximo
Causa raíz: contención de locks (locks de fila, índices calientes, locks de metadata), o colapso de planificación de hilos por demasiadas conexiones.
Solución: identifica bloqueadores vía processlist/performance schema; acorta transacciones; añade o ajusta índices; arregla pooling de conexiones; considera thread pool para control de admisión.
3) Síntoma: réplicas se atrasan impredeciblemente después de horas pico
Causa raíz: binlog ráfaga por transacciones largas; apply en réplica limitado por apply single-threaded o por contención de recursos; lecturas pesadas en réplica que ahogan el apply.
Solución: reduce transacciones largas; habilita y ajusta replicación paralela donde esté soportada; aísla cargas de reporting; asegura que la réplica tenga suficiente IO/CPU.
4) Síntoma: “la base de datos está lenta” solo durante backups
Causa raíz: contención de IO por backups o amplificación de snapshot; la herramienta de backup compite por caché y ancho de banda de disco.
Solución: limita el backup; prográmalo fuera de pico; usa réplicas para backups; valida que el método de backup se alinee con el sistema de archivos y almacenamiento.
5) Síntoma: uso de memoria crece, luego OOM o thrash de swap
Causa raíz: memoria por conexión multiplicada por muchas sesiones (sort buffers, tmp tables, join buffers), más caches sobredimensionados.
Solución: limita conexiones; ajusta buffers por hilo; usa thread pool; mide la huella real de memoria; evita “simplemente aumentar tmp_table_size” como reflejo.
6) Síntoma: planes de consulta regresan después de la migración
Causa raíz: diferencias de optimizador, comportamiento de estadísticas cambiado, distintos valores predeterminados para modos SQL o ajustes de motor entre forks/versiones.
Solución: ejecuta pruebas de regresión de explain plan sobre datos reales; fija índices donde corresponda; alinea modos SQL; no asumas “drop-in” sin pruebas de carga.
7) Síntoma: alta latencia de fsync, commits lentos
Causa raíz: latencia de escritura del almacenamiento, configuración de sync_binlog, interacciones entre doublewrite y journaling del sistema de archivos, o contención IO por vecinos ruidosos.
Solución: mide await del dispositivo; asegura que el método de flush sea apropiado (frecuentemente O_DIRECT); separa logs si es necesario; actualiza almacenamiento o cambia durabilidad conscientemente.
Listas de comprobación / plan paso a paso
Checklist A: Elegir entre MariaDB y Percona Server para producción
- Requisito de compatibilidad: si un proveedor especifica soporte de versión MySQL, inclínate hacia Percona Server en esa línea mayor de MySQL.
- Herramientas operativas: si ya usas flujos del toolkit de Percona (digests, análisis) y quieres mínima deriva de comportamiento, Percona Server es el camino de menor fricción.
- Necesidad de función: si necesitas características específicas de MariaDB (modos de replicación, opciones de motor), acepta el trade-off de compatibilidad y prueba más exhaustivamente.
- Habilidad del equipo: si tu equipo depura con Performance Schema y conocimiento de MySQL upstream, Percona Server mapea más directamente. Si tu equipo ya opera MariaDB a escala, no cambies por ideología.
- Camino de upgrades: elige el fork cuyo ritmo de actualizaciones puedas seguir realmente. Las bases de datos estancadas son donde los “ajustes de rendimiento” van a morir.
Checklist B: Tuning seguro en cualquiera de los forks (hacer en este orden)
- Activa el log de consultas lentas con umbrales sensatos, rota los logs y crea el hábito de un digest semanal.
- Confirma el dimensionamiento del buffer pool y verifica si el working set cabe; no adivines—mide el churn de lecturas en el tiempo.
- Valida la latencia y utilización del almacenamiento bajo carga; arregla el IO antes de “afinar la base de datos”.
- Define la durabilidad intencionalmente; documenta cualquier compromiso y alinéalo con el riesgo del negocio.
- Arregla las consultas top por tiempo total, no por ego. Las consultas frecuentes “rápidas” pueden ser tu verdadero centro de costos.
- Elimina transacciones largas y DDL sorpresa durante picos. Provocan bloqueos y patologías de replicación.
- Controla las conexiones: pooling, timeouts, circuit breakers; considera thread pooling como estabilizador.
- Prueba carga con distribuciones de datos similares a producción; el optimizador cambia comportamiento con sesgos.
Checklist C: Plan de migración (MariaDB ↔ Percona Server) sin apostar
- Inventario de dependencias: modos SQL, plugins de autenticación, ajustes de replicación, herramientas de backup y requisitos de proveedores.
- Diff de configs: extrae
SHOW VARIABLESde ambos; identifica perillas específicas del fork; elimina desconocidos. - Reproduce la carga: usa un entorno de staging con copia del esquema de producción + datos representativos; ejecuta cargas capturadas si es posible.
- Plan de rollback: ruta de restauración o promoción de réplica probada; tiempo, documenta y ensaya.
- Corte vía replicación: establece una réplica en el nuevo motor, valida checksums y luego cambia tráfico en una ventana controlada.
- Guardarraíles post-cutover: ajusta max connections, habilita slow logs, verifica replicación y vigila latencia de commits y esperas por locks.
Preguntas frecuentes
1) ¿Cuál es más rápido: MariaDB o Percona Server?
Ninguno, de forma fiable, en el sentido que la gente suele querer. Para muchas cargas OLTP, el cuello de botella es IO, locking o consultas malas, no el fork.
Elige según compatibilidad, operabilidad y las funcionalidades que realmente usarás.
2) ¿Percona Server es solo “MySQL con parches”?
Prácticamente sí: busca mantenerse cercano a MySQL upstream añadiendo mejoras operativas. Esa cercanía suele ser la mayor ventaja si valoras comportamiento predecible y compatibilidad.
3) ¿Sigue siendo MariaDB un reemplazo drop-in para MySQL?
A veces, para aplicaciones simples y emparejamiento cuidadoso de versiones. Pero a más largo plazo la divergencia importa.
Trata “drop-in” como una hipótesis que pruebas con carga real y regresión de explain plan, no como una promesa.
4) ¿El thread pool resolverá mi problema de max_connections alto?
Puede mitigar el colapso del servidor controlando la concurrencia, pero no arreglará el comportamiento subyacente de la aplicación.
Aún necesitas pooling correcto, timeouts sensatos y backpressure para evitar tormentas de reintentos.
5) ¿Los “ajustes de rendimiento” se tratan principalmente de cambiar parámetros de InnoDB?
El mayor ROI suele ser trabajo en consultas/índices y disciplina de conexiones. Los ajustes de InnoDB importan, pero son de segundo orden a menos que ya seas disciplinado en lo básico.
6) ¿Cómo sé si las mejoras de rendimiento vienen de reducir la durabilidad?
Revisa innodb_flush_log_at_trx_commit y sync_binlog. Si están relajados, tu benchmark puede verse genial porque no pagas por persistencia en cada commit.
7) ¿Debería habilitar todos los instrumentos de Performance Schema para depurar?
No por defecto. Habilita lo que necesites, muestrea responsablemente y conoce la sobrecarga. Una buena práctica es una configuración base siempre activa,
más un “modo incidente” documentado que puedas habilitar brevemente.
8) ¿Cuál es la forma más sencilla de mejorar el lag de replicación?
Elimina transacciones largas en el primario, reduce ráfagas de escritura y da a las réplicas suficiente IO/CPU. Luego ajusta apply paralelo si tu versión lo soporta y tu carga lo beneficia.
9) Si estoy limitado por IO, ¿cambiar de fork es inútil?
En gran medida, sí. Las diferencias entre forks no cambian la latencia de tu dispositivo. Dedica esfuerzos a diseño de almacenamiento, comportamiento de flush,
checkpointing y patrones de escritura de la carga. La elección del fork ayuda con herramientas y guardarraíles, no con los límites básicos de IO.
10) ¿Qué debería estandarizar entre entornos para evitar “iba más rápido en staging”?
Línea de versión MySQL/MariaDB, variables de configuración, esquema e índices, forma del dataset y clase de almacenamiento. Especialmente la clase de almacenamiento.
Tener staging en NVMe local rápido y producción en discos en red es una forma clásica de aprender humildad.
Siguientes pasos que puedes hacer esta semana
- Ejecuta la guía de diagnóstico rápido una vez durante un pico normal y captura salidas base: vmstat, iostat, líneas clave de InnoDB status y digest de slow query.
- Elige las 3 consultas top por tiempo total del slow log y arréglalas con índices o reduciendo frecuencia. Despliega esos cambios antes de tocar perillas exóticas del servidor.
- Audita ajustes de durabilidad y escribe la decisión del negocio. Si estás corriendo durabilidad relajada por accidente, vives a crédito.
- Limita y controla conexiones: alinea pools de la app con núcleos de CPU y presupuesto de IO; deja de tratar
max_connectionscomo estrategia de escalado. - Decide el fork según restricciones: compatibilidad de proveedor y cadencia de upgrades primero; afirmaciones de “más rápido” al final.
- Ensaya una restauración desde tus backups. Si no puedes restaurar con calma, no tienes backups—tienes esperanza cara.
Si quieres una recomendación directa: elige Percona Server cuando necesites compatibilidad con MySQL upstream y predictibilidad operativa.
Elige MariaDB cuando explícitamente quieras el ecosistema/características de MariaDB y estés dispuesto a probar y asumir la divergencia.
En ambos casos, la base de datos más rápida es la que tiene menos consultas malas, menos transacciones sorpresa y un almacenamiento que no miente sobre su latencia.