Compraste NVMe. Las tablas del benchmark parecían una experiencia religiosa. Luego llegó producción: picos de latencia p99, CPU “idle”
pero consultas atascadas, y un gráfico de almacenamiento que parece un cardiograma. Alguien dice “es solo flushing”, otro sugiere “aumenta io_capacity”,
y un tercero propone “apaga la durabilidad para rendimiento” como si eso fuera una frase madura.
Esta es la guía práctica para ejecutar MySQL o MariaDB en NVMe sin adivinar. Afinaremos redo logs, comportamiento de flush y capacidad de E/S
con comandos que puedes ejecutar hoy—más los modos de fallo que encontrarás si no lo haces bien.
Hechos y contexto histórico (lo que explica las rarezas)
- InnoDB no nació en la era del flash. Las suposiciones iniciales de InnoDB encajaban con discos giratorios: las escrituras secuenciales y ordenadas eran primordiales; lo aleatorio era caro. NVMe cambia ese perfil de dolor.
- El doublewrite existe por las páginas partidas. La pérdida de energía durante la escritura de una página de 16KB puede dejar datos mitad antiguos, mitad nuevos. Doublewrite es el “cinturón de seguridad”.
- MySQL 8.0 cambió el formato de redo logging. El “nuevo redo” (y el trabajo continuo sobre escrituras atómicas) cambió algunas características de rendimiento y recuperación respecto a versiones antiguas.
- MariaDB se desvió en funciones y valores por defecto. Conservó InnoDB (como XtraDB antes), pero el comportamiento, variables y detalles de implementación difieren lo suficiente para importar al ajustar.
- Group commit es la razón por la que puedes tener durabilidad y rendimiento. Muchas transacciones comparten un fsync cuando el motor las agrupa. Tu carga de trabajo decide si obtienes esa ventaja.
- NVMe no es “un disco más rápido”. Es una interfaz diferente con colas profundas y paralelismo. Una carga de fsyncs de un solo hilo puede seguir pareciendo lenta.
- El checkpointing es el impuesto oculto. Los redo logs permiten que InnoDB difiera el flush, pero la factura llega como presión de checkpoint y trabajo del limpiador de páginas.
- La capa de bloque de Linux evolucionó para el flash. Los ajustes antiguos asumían SATA/SAS; el NVMe moderno a menudo quiere scheduler “none” y ajustes cuidadosos de writeback más que viejos trucos de cola.
- El NVMe en la nube a veces es “con forma de NVMe”. Muchas instancias exponen dispositivos NVMe respaldados por almacenamiento en red. La variación de latencia puede ser real incluso si el rendimiento es alto.
Una cita para pegar en un post-it cuando te tiente “ajustarlo hasta que sea rápido”:
La esperanza no es una estrategia.
— General Gordon R. Sullivan
Modelo mental NVMe + InnoDB: qué está pasando realmente
La manera más fácil de perder una semana es tratar a InnoDB como “escribe cosas en disco” y a NVMe como “escribe cosas rápido”.
En producción, lo que importa es qué escrituras, cuándo ocurren y qué tan espigadas son.
Tres flujos de E/S que debes separar en tu cabeza
- Escrituras de redo log: apéndices secuenciales a los archivos ib_logfile/redo. Están gobernadas por la política de flush y el coste de fsync.
- Flush de páginas de datos: páginas sucias de 16KB escritas a los tablespaces como actividad en segundo plano (page cleaners) o bajo presión.
- Escrituras doublewrite: escrituras extra para hacer que los flush de páginas sean seguros ante fallos (a menos que uses escrituras atómicas / configuraciones que lo cambien).
NVMe mejora todos ellos, pero no por igual. Las escrituras de redo suelen ser pequeñas y sensibles a la latencia (fsync). Los flushes de páginas requieren mucho ancho de banda.
Doublewrite puede convertirse en “muerte por mil escrituras pequeñas” si está mal configurado, o ser casi invisible si se alinea con las fortalezas del dispositivo.
Por qué sufres picos de latencia p99 incluso con NVMe rápido
InnoDB es un contador. Te permite gastar crédito de E/S almacenando páginas sucias en memoria. Cuando debe pagar la deuda—porque el redo está casi lleno,
o el porcentaje de páginas sucias es alto, o un checkpoint debe avanzar—puede obligar a hilos en primer plano a ayudar a flushar.
Ahí es donde aparecen los bloqueos: transacciones esperando al flush del log, al flush de páginas o a ambos.
Broma #1: NVMe hace más rápidas las malas políticas de flush, como darle espresso a un niño pequeño—las cosas pasan más rápido, pero no mejor.
MySQL vs MariaDB: qué difiere en redo/flush/capacidad de E/S
Ambos motores hablan InnoDB, pero no incluyen el mismo InnoDB, y no siempre se comportan igual. Si copias una guía de ajuste
de uno al otro, puedes caer en el valle inquietante: “arranca, pero está embrujado”.
Diferencias en la configuración de redo log
- MySQL 8.0: usa
innodb_redo_log_capacity(perilla moderna) y gestiona los archivos de redo internamente. Muchas guías antiguas siguen hablando deinnodb_log_file_size; eso es el mundo viejo. - MariaDB: comúnmente usa
innodb_log_file_sizeyinnodb_log_files_in_group(según la versión). Puede que no exponga la misma abstracción de capacidad de redo.
Capacidad de E/S y hilos en segundo plano
Las semánticas de innodb_io_capacity y innodb_io_capacity_max son similares en espíritu, pero no asumas comportamiento idéntico bajo presión.
El ajuste del page cleaner y las opciones de flush neighbor también pueden diferir.
Escrituras atómicas y comportamiento de doublewrite
Aquí es donde el “tuning NVMe” se vuelve real. Algunas implementaciones confían en garantías del sistema de ficheros + dispositivo (escrituras atómicas de 16KB, comportamiento DAX, o características del dispositivo)
para reducir la necesidad de doublewrite. Muchas no. Y el NVMe en la nube a menudo no se comporta como tu SSD de laboratorio.
Trata el doublewrite como obligatorio a menos que pruebes que toda tu pila resiste páginas partidas.
Conclusión operacional: elige un servidor (MySQL o MariaDB), y luego ajusta usando las variables y contadores de estado de ese servidor.
“Mismo InnoDB” es suficientemente cercano para diagramas de arquitectura, no para evitar outages.
Redo logs en NVMe: tamaño, disposición y dinámica de checkpoints
Los redo logs son tu amortiguador. Una mayor capacidad de redo permite a InnoDB acumular más trabajo sucio y flushar de forma más suave.
Pero “más grande es mejor” se convierte en “la recuperación tarda una eternidad” si exageras, y puede enmascarar un problema de flushing hasta que sea una sorpresa a las 3 a.m.
Qué te compran realmente los redo logs
- Desacoplan el commit del flush de páginas (en su mayor parte). Commit escribe redo; las páginas pueden flusharse después.
- Suavizan patrones de escritura aleatoria en apéndices secuenciales en la medida de lo posible.
- Fijan el presupuesto de checkpoint. Si el checkpoint no puede avanzar, sufrirás stalls.
Realidad específica de NVMe: redo trata sobre la variación de latencia, no sobre el throughput
Muchos dispositivos NVMe pueden lograr rendimientos ridículos, pero la variación de latencia de fsync bajo presión es lo que te mata.
La ruta de redo log es sensible a:
- el comportamiento de la caché de escritura del dispositivo y semánticas FUA/flush,
- el modo de journal del sistema de ficheros,
- la congestión del writeback del kernel,
- los estallidos de flush de páginas en segundo plano que compiten por las mismas colas del dispositivo.
Dimensionamiento de redo: orientación con opinión
En sistemas modernos, un redo subdimensionado es una lesión autoinfligida. Si tu capacidad de redo es pequeña, harás checkpoints constantemente,
y los page cleaners entrarán en thrash. Eso convierte “NVMe rápido” en “por qué el commit tarda 40 ms a veces”.
Haz esto en su lugar:
- Tamaño de redo para que los picos de escritura en estado estable no choquen con la presión de “log full”.
- Valida las expectativas de tiempo de recuperación. Un redo grande implica más redo que escanear durante la recuperación tras fallo.
- Observa la edad del checkpoint y el porcentaje de páginas sucias; ajústalos para evitar patrones en sierra.
Cuando el redo es demasiado grande
Si operas en un nodo donde el tiempo de reinicio tiene un presupuesto estricto—piensa en autoscaling o SLAs estrictos de failover—un redo demasiado grande puede hacer la recuperación lenta.
Eso no es teórico. Es la diferencia entre un failover que parece un “tic” y uno que exige una mesa de incidentes.
Política de flush: perillas de durabilidad y su coste
Hay dos clases de personas en bases de datos: las que han perdido datos y las que aún no. La política de flush decide en cuál campamento estarás.
innodb_flush_log_at_trx_commit: la gran palanca
Esta configuración decide cuándo InnoDB flusha el redo a almacenamiento duradero. Valores comunes:
- 1: escribir y fsync del redo en cada commit. Mejor durabilidad; mayor sensibilidad a la latencia de fsync.
- 2: escribir en commit, fsync una vez por segundo. Puede perder hasta ~1 segundo de transacciones en un crash/pérdida de energía.
- 0: flush una vez por segundo; ventana de pérdida potencial mayor.
Mi opinión: usa 1 para la mayoría de sistemas de producción que importan, e invierte en que fsync sea predecible.
Usa 2 solo cuando el negocio acepte explícitamente la ventana de pérdida y la tengas documentada en el runbook.
sync_binlog: no olvides la durabilidad de la replicación
Si usas binlogs (replicación, recuperación punto en el tiempo, CDC), sync_binlog interactúa con la durabilidad también.
Un “ups” común es tener redo duradero pero binlog no duradero, y luego preguntarse por qué un crash causa inconsistencias en replicación o huecos en PITR.
El sistema de ficheros y las opciones de montaje importan más que muchos “ajustes de BD”
En Linux, ext4 y XFS se comportan distinto ante fsync. El modo de journaling y las barreras importan.
Si usas volúmenes en la nube, el dispositivo de bloque puede mentir sobre las semánticas de caché de formas que “están bien” hasta el día que no lo están.
Por eso a los SRE les cuesta confiar en un gráfico que se ve demasiado limpio.
Broma #2: Desactivar fsync por rendimiento es como quitar el detector de humo porque hace ruido.
Capacidad de E/S bien hecha: io_capacity, E/S en segundo plano y páginas sucias
innodb_io_capacity no es “qué tan rápido es tu disco”. Es una pista a InnoDB sobre con qué agresividad debe flushar en segundo plano.
Si lo pones demasiado bajo, las páginas sucias se acumulan y el flushing se vuelve explosivo. Si es demasiado alto, puedes crear presión de escritura constante
que compita con las lecturas y aumente la latencia.
El objetivo: flushing constante, no heroico
Tu mejor caso es aburrido: los page cleaners flushan continuamente a una tasa que mantiene las páginas sucias dentro de una banda estable,
la edad del checkpoint saludable y el consumo de redo suave. Tu peor caso es “silencio, silencio, pánico de flush”, que
parece acantilados periódicos de latencia.
Cómo elegir valores iniciales
- Empieza con una línea base realista. NVMe puede hacer decenas de miles de IOPS, pero los patrones de flush de InnoDB no son escrituras 4k aleatorias crudas.
- Mide IOPS de escritura sostenidos y latencia bajo carga de base de datos, no con un benchmark en caja vacía.
- Usa
innodb_io_capacity_maxcomo “techo de ráfaga”, no como plan diario.
La gestión de páginas sucias es el indicador líder
Si las páginas sucias crecen de forma continua durante tráfico normal y luego colapsan de repente durante stalls, el flushing en segundo plano es insuficiente.
Si las páginas sucias se mantienen bajas pero ves E/S de escritura constante y latencia de lectura elevada, quizá estés sobresaturando el flushing.
Linux + NVMe: ajustes que importan (y qué es placebo)
Existe todo un género de “tuning NVMe” que es básicamente culto de la carga. La caja es rápida; el cuello de botella suele ser semántica de flush,
contención de colas, planificación de CPU o comportamiento del sistema de ficheros. Dicho esto, algunas comprobaciones en Linux merecen tu tiempo.
Scheduler de E/S: normalmente none para NVMe
Para NVMe, la capa de bloque multiqueue del kernel hace que los schedulers tradicionales a menudo no ayuden. Muchas distribuciones ya vienen por defecto con none.
Confirma, no asumas.
Writeback y ratios de páginas sucias: evita tormentas sincronizadas
El writeback de páginas sucias del kernel puede sincronizarse con el flushing de InnoDB y producir congestión periódica.
No “lo arreglas” con cambios aleatorios de sysctl; mides si los picos de writeback se correlacionan con stalls de BD,
y luego ajustas con cuidado.
Frecuencia de CPU e interrupciones: la sabotaje silencioso
NVMe + alta QD puede exigir mucho CPU. Si tu CPU reduce frecuencia agresivamente, o las interrupciones están mal distribuidas,
tu “almacenamiento rápido” se convierte en un calentador caro. Las rutas sensibles a latencia de fsync odian el jitter.
TRIM/discard: trátalo con respeto
El discard en línea puede introducir picos de latencia en algunos dispositivos o pilas. Muchos operadores prefieren fstrim periódico
durante ventanas de mantenimiento. La calidad del firmware NVMe varía, y no vas a poder tunear un mal día de firmware.
Tareas prácticas (comandos + salida + la decisión)
Estas son tareas reales que puedes ejecutar en un host Linux con MySQL o MariaDB. Cada una incluye qué significa la salida
y la decisión que tomas a partir de ella. No ejecutes todas a la vez en producción. Elige las que coincidan con tus síntomas.
Task 1: Confirmar si estás en MySQL o MariaDB (y la versión)
cr0x@server:~$ mysql --version
mysql Ver 8.0.36 for Linux on x86_64 (MySQL Community Server - GPL)
Qué significa: Estás en MySQL 8.0, así que el dimensionamiento de redo probablemente use innodb_redo_log_capacity en lugar de perillas antiguas.
Decisión: Usa los nombres de variables y contadores de MySQL 8.0; no apliques ajustes exclusivos de MariaDB.
Task 2: Capturar configuraciones clave de durabilidad de InnoDB
cr0x@server:~$ mysql -Nse "SHOW VARIABLES WHERE Variable_name IN ('innodb_flush_log_at_trx_commit','sync_binlog','innodb_doublewrite','innodb_flush_method');"
innodb_doublewrite ON
innodb_flush_log_at_trx_commit 1
innodb_flush_method O_DIRECT
sync_binlog 1
Qué significa: Esta es la postura “durable por defecto” (redo y binlog sincronizados). O_DIRECT evita el doble cacheo.
Decisión: Manténlo salvo que el negocio acepte ventanas de pérdida; optimiza la previsibilidad de fsync en lugar de bajar perillas.
Task 3: Comprobar variables de dimensionamiento de redo (MySQL 8.0)
cr0x@server:~$ mysql -Nse "SHOW VARIABLES LIKE 'innodb_redo_log_capacity';"
innodb_redo_log_capacity 2147483648
Qué significa: La capacidad de redo es 2GiB. Para cargas de escritura intensivas, eso puede ser pequeño y aumentar la presión de checkpoints.
Decisión: Si ves stalls de checkpoint o “log waits”, planifica una ventana de cambio para aumentar la capacidad de redo y valida el impacto en tiempo de recuperación.
Task 4: Comprobar variables de redo (estilo MariaDB)
cr0x@server:~$ mysql -Nse "SHOW VARIABLES WHERE Variable_name IN ('innodb_log_file_size','innodb_log_files_in_group');"
innodb_log_file_size 268435456
innodb_log_files_in_group 2
Qué significa: El redo total es ~512MiB. Eso suele estar subdimensionado en OLTP respaldado por NVMe donde los picos de escritura son comunes.
Decisión: Considera aumentar el redo total, pero ten en cuenta el procedimiento operativo (recreación de archivos al reiniciar en muchas configuraciones).
Task 5: Comprobar porcentaje de páginas sucias y presión del buffer pool
cr0x@server:~$ mysql -Nse "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_%';"
Innodb_buffer_pool_pages_data 1048576
Innodb_buffer_pool_pages_dirty 196608
Innodb_buffer_pool_pages_free 1024
Innodb_buffer_pool_pages_total 1050624
Qué significa: Las páginas sucias son ~18.7% (196608/1050624). Las páginas libres están cerca de cero, así que el buffer pool está completamente utilizado.
Decisión: Si las páginas sucias suben con carga estable, aumenta el flushing en segundo plano (innodb_io_capacity) o arregla la contención de E/S. Si oscilan salvajemente, reduce la ráfaga (dimensionamiento de redo, ajuste de flush).
Task 6: Comprobar comportamiento de checkpoints vía status de InnoDB
cr0x@server:~$ mysql -Nse "SHOW ENGINE INNODB STATUS\G" | sed -n '1,120p'
*************************** 1. row ***************************
Type: InnoDB
Name:
Status:
=====================================
2025-12-31 12:14:51 0x7f3c2c0c4700 INNODB MONITOR OUTPUT
=====================================
Log sequence number 879112345678
Log flushed up to 879112300000
Pages flushed up to 879110900000
Last checkpoint at 879110900000
Qué significa: El LSN avanza (escrituras), el flush va retrasado, y el checkpoint equivale a “Pages flushed up to”. Si Log sequence number avanza muy por delante de Last checkpoint, estás acumulando edad de checkpoint.
Decisión: Si la edad del checkpoint crece hasta causar stalls, aumenta la capacidad de redo y/o sube la capacidad de E/S; también revisa picos de latencia de fsync y el overhead del doublewrite.
Task 7: Medir esperas relacionadas con fsync en Performance Schema (MySQL)
cr0x@server:~$ mysql -Nse "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 event_name LIKE 'wait/io/file/innodb/innodb_log_file%' ORDER BY sum_timer_wait DESC LIMIT 5;"
wait/io/file/innodb/innodb_log_file 1289345 842.11
Qué significa: Se está gastando mucho tiempo esperando IO del redo log. Eso suele ser la latencia de commit.
Decisión: Correlaciona con la latencia de commit p99. Si es alta, céntrate en la ruta de flush: variación de latencia del dispositivo, journaling del sistema de ficheros, congestión del kernel.
Task 8: Comprobar contadores de espera de log y presión de escritura
cr0x@server:~$ mysql -Nse "SHOW GLOBAL STATUS WHERE Variable_name IN ('Innodb_log_waits','Innodb_log_write_requests','Innodb_os_log_fsyncs','Innodb_os_log_written');"
Innodb_log_waits 413
Innodb_log_write_requests 98234567
Innodb_os_log_fsyncs 4512390
Innodb_os_log_written 9876543210
Qué significa: Innodb_log_waits distinto de cero sugiere que las transacciones tuvieron que esperar porque el buffer de log/espacio de redo estaba restringido.
Decisión: Si los log waits aumentan durante la carga normal, aumenta la capacidad de redo y/o arregla el checkpointing y el rendimiento de flush.
Task 9: Confirmar modelo de dispositivo NVMe, firmware y enlace PCIe (sanidad hardware)
cr0x@server:~$ sudo nvme list
Node SN Model Namespace Usage Format FW Rev
/dev/nvme0n1 S6X... SAMSUNG MZVL21T0HCLR-00B00 1 900.19 GB / 1.00 TB 512 B + 0 B GXA7401Q
Qué significa: Sabes en qué dispositivo estás ejecutando, incluido el firmware. El firmware importa para picos de latencia.
Decisión: Si ves stalls periódicos, revisa si el dispositivo tiene problemas de firmware conocidos en tu flota; considera actualizaciones de firmware controladas.
Task 10: Comprobar scheduler de E/S para NVMe
cr0x@server:~$ cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline kyber bfq
Qué significa: El scheduler es none, que suele ser correcto para NVMe.
Decisión: Déjalo salvo que tengas fuerte evidencia de que otro scheduler mejora la latencia tail bajo contención mixta lectura/escritura.
Task 11: Comprobar sistema de ficheros y opciones de montaje (las semánticas de flush viven aquí)
cr0x@server:~$ findmnt -no SOURCE,FSTYPE,OPTIONS /var/lib/mysql
/dev/nvme0n1p2 ext4 rw,relatime,errors=remount-ro,data=ordered
Qué significa: ext4 con data=ordered. Esto afecta el comportamiento del journaling y puede influir en el coste de fsync.
Decisión: Si la latencia de fsync es volátil, prueba alternativas de sistema de ficheros u opciones de montaje en staging; no cambies montados en producción a la ligera.
Task 12: Comprobar ajustes de writeback del kernel
cr0x@server:~$ sysctl vm.dirty_background_ratio vm.dirty_ratio vm.dirty_expire_centisecs vm.dirty_writeback_centisecs
vm.dirty_background_ratio = 10
vm.dirty_ratio = 20
vm.dirty_expire_centisecs = 3000
vm.dirty_writeback_centisecs = 500
Qué significa: El kernel empezará writeback de fondo alrededor del 10% sucio y limitará alrededor del 20% sucio. Estos valores por defecto pueden estar bien, o pueden sincronizarse mal con el flushing de InnoDB.
Decisión: Si observas congestión global periódica de E/S, considera ajustar los ratios a la baja para incentivar writeback temprano, pero valida el impacto con carga real.
Task 13: Observar latencia E/S y colas en tiempo real
cr0x@server:~$ iostat -x 1 5
Linux 6.1.0 (server) 12/31/2025 _x86_64_ (32 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
6.12 0.00 2.11 3.95 0.00 87.82
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz aqu-sz %util
nvme0n1 820.0 52500.0 0.0 0.0 1.20 64.0 1600.0 78000.0 0.0 0.0 8.70 48.8 15.2 92.0
Qué significa: Las escrituras tienen ~8.7ms await con alta utilización. Eso no es “NVMe rápido” en la forma que tus commits desean.
Decisión: Si la latencia de commit se correlaciona con w_await, reduce picos de escritura (dimensionamiento de redo, ajuste de io_capacity) e investiga saturación del dispositivo o vecinos ruidosos.
Task 14: Identificar principales consumidores de E/S de MySQL a nivel OS
cr0x@server:~$ sudo pidstat -d 1 3 -p $(pidof mysqld)
Linux 6.1.0 (server) 12/31/2025 _x86_64_ (32 CPU)
12:16:03 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
12:16:04 999 24187 12000.00 54000.00 0.00 52 mysqld
12:16:05 999 24187 11500.00 61000.00 0.00 61 mysqld
Qué significa: MySQL es el generador de escritura. Bien. Si otro proceso está escribiendo mucho, puede robar E/S y destruir la latencia tail.
Decisión: Si existe contención de E/S, aísla MySQL (volumen dedicado, cgroups/io.max, dispositivo separado, o elimina al vecino ruidoso).
Task 15: Comprobar ajustes de capacidad de E/S de InnoDB en vivo
cr0x@server:~$ mysql -Nse "SHOW VARIABLES WHERE Variable_name IN ('innodb_io_capacity','innodb_io_capacity_max','innodb_page_cleaners');"
innodb_io_capacity 200
innodb_io_capacity_max 2000
innodb_page_cleaners 4
Qué significa: innodb_io_capacity=200 es un valor de la era de discos giratorios. En NVMe, suele ser demasiado bajo, lo que lleva a acumulación de páginas sucias y eventual stalls.
Decisión: Aumenta gradualmente (p. ej., 1000–5000 según la carga y el dispositivo), observa páginas sucias, edad del checkpoint y latencia de lectura. No saltes a 20000 porque lo viste en un foro.
Task 16: Confirmar doublewrite y ajustes relacionados con escrituras atómicas (gestión de riesgo)
cr0x@server:~$ mysql -Nse "SHOW VARIABLES LIKE 'innodb_doublewrite';"
innodb_doublewrite ON
Qué significa: El doublewrite está activado, lo que protege contra páginas partidas.
Decisión: Déjalo activado a menos que tengas una pila de escrituras atómicas validada y un plan de recuperación tras fallo probado que demuestre que es seguro cambiarlo.
Guion de diagnóstico rápido (primeras/segundas/terceras comprobaciones)
Cuando la latencia sube y todos miran el mismo dashboard, necesitas un camino corto hacia “¿qué es esto, realmente?”
Aquí está la secuencia que encuentra el cuello de botella rápidamente en la mayoría de incidentes con MySQL/MariaDB en NVMe.
Primero: ¿es latencia de fsync de redo o presión de flush de datos?
- Comprueba latencia de commit y log waits:
Innodb_log_waits, esperas de redo en Performance Schema, y tiempos de commit de la aplicación. - Revisa
iostat -xpor picos dew_awaity alto %util.
Si la latencia de fsync de redo es lenta: céntrate en la variación de latencia del dispositivo, journaling del sistema de ficheros, caché de escritura y contención de E/S. El dimensionamiento de redo puede ayudar indirectamente al suavizar checkpoints, pero no arreglará una ruta de fsync mala.
Segundo: ¿los page cleaners van rezagados?
- Observa el porcentaje de páginas sucias a lo largo del tiempo.
- Comprueba el estado de InnoDB por crecimiento de edad de checkpoint y actividad de flushing.
- Confirma que
innodb_io_capacityno esté configurado como si fuera 2009.
Si los cleaners van atrás: sube la capacidad de E/S gradualmente, considera aumentar la capacidad de redo y busca amplificación de escritura por doublewrite + sistema de ficheros.
Tercero: ¿es latencia de lectura causada por contención de escritura?
- Si las lecturas se ralentizan durante tormentas de flush, tienes contención en colas de E/S.
- Comprueba si el writeback de fondo del kernel se alinea con los stalls de BD (ajustes de writeback del kernel).
Si la latencia de lectura sigue a picos de escritura: reduce la ráfaga de flushing (redo/io capacity), aísla E/S y verifica scheduler y comportamiento de CPU.
Cuarto (cuando es “raro”): prueba si el NVMe es realmente NVMe
- En la nube, confirma el comportamiento real de la capa de almacenamiento mediante la variación de latencia observada, no por el nombre del dispositivo.
- Busca otros escritores y throttling en el hipervisor o la capa de volúmenes.
Tres historias corporativas desde el frente
1) El incidente causado por una suposición errónea: “NVMe significa que fsync es barato”
Una empresa tipo fintech migró un clúster MySQL de SSD SATA a NVMe local en hosts nuevos. El equipo esperaba lo habitual:
menor latencia, mayor throughput, menos incidentes relacionados con E/S. Obtuvieron los dos primeros en la mediana, y el último empeoró.
El síntoma fue clásico: p95 bien, p99.9 periódicamente se iba al abismo. Los hilos de la aplicación se amontonaban esperando commits.
El on-call miró la CPU y vio mucho idle. El throughput de almacenamiento no estaba cerca de los límites del dispositivo. “Entonces no puede ser disco”, dijo alguien,
y la sala asintió porque los gráficos son persuasivos.
La suposición errónea fue que NVMe automáticamente hace fsync predecible. En realidad, su sistema de ficheros + opciones de montaje
junto con el housekeeping interno del firmware del dispositivo creaban stalls periódicos de fsync bajo presión sostenida de escritura.
Los commits se bloqueaban por esos stalls porque ejecutaban innodb_flush_log_at_trx_commit=1 (correcto) y no tenían margen cuando el jitter de fsync apareció.
La solución no fue “bajar durabilidad”. Midieron esperas de redo fsync, las correlacionaron con picos de iostat,
probaron otra configuración de sistema de ficheros en staging, y añadieron margen aumentando la capacidad de redo para que los checkpoints fueran menos explosivos.
La latencia tail se estabilizó. La sorpresa fue que el almacenamiento “más rápido” produjo la latencia más volátil hasta que la pila fue ajustada para previsibilidad.
2) La optimización que salió mal: subir innodb_io_capacity a la luna
Otra compañía tenía un clúster MariaDB sirviendo un servicio con muchas escrituras. Vieron subir páginas sucias en picos y pensaron que el flushing era el cuello de botella.
Alguien fijó innodb_io_capacity a un valor que parecía razonable comparado con un benchmark sintético NVMe, y lo aplicaron en mantenimiento.
Durante aproximadamente una hora, todo mejoró. Las páginas sucias se mantuvieron bajas y los dashboards se volvieron verdes. Luego la latencia de lectura empezó a subir,
no solo en consultas pesadas sino en búsquedas puntuales simples. La tasa de aciertos del buffer pool bajó ligeramente. La aplicación empezó a reintentar por timeouts.
Todos culparon a “la red” porque eso es lo que hace la gente cuando se supone que el almacenamiento está resuelto.
La realidad: forzaron a los page cleaners a flushar agresivamente todo el tiempo, creando presión de escritura constante y saturando las colas del dispositivo.
Las lecturas tuvieron que competir con una manguera de escritura en segundo plano. El throughput NVMe era alto, pero la latencia tail para lecturas empeoró. Fue un problema de planificación de E/S que ellos mismos crearon.
Volvieron a un valor menor de io_capacity y luego re-ajustaron gradualmente mientras observaban latencia de lectura y páginas sucias juntas.
El patrón correcto fue una tasa de flushing moderada con un máximo sensato, más un aumento de la capacidad de redo para evitar pánicos de checkpoint.
La lección: “más flushing” no es igual a “mejor flushing”.
3) La práctica aburrida pero correcta que salvó el día: validar tiempos de recuperación
Un proveedor SaaS ejecutaba MySQL con capacidad de redo relativamente grande porque su carga tenía escrituras en ráfaga. Eso hizo el sistema suave bajo carga.
Pero hicieron algo poco glamoroso: probaron regularmente el tiempo de recuperación tras fallo en staging con volumen de datos y utilización de redo similares a producción.
Durante un evento en un centro de datos, un primario colapsó y se reinició. El plan de failover asumía un presupuesto de tiempo de reinicio.
Porque lo habían probado, sabían exactamente qué esperar y ya habían ajustado la capacidad de redo para mantenerse dentro del tiempo de recuperación.
La replicación se puso al día limpiamente porque las opciones de durabilidad del binlog coincidían con la postura de durabilidad.
Mientras otros equipos discutían si reconstruir desde backup o promover un réplica, este equipo siguió el runbook:
confirmar el progreso de la recuperación de redo, monitorizar las etapas de recuperación tras fallo y mantener el tráfico drenado hasta que el motor reportó estado consistente.
El resultado no fue glamoroso. Fue un incidente contenido sin pérdida de datos y sin una restauración de varias horas.
La práctica “aburrida” fue medir la recuperación por adelantado y negarse a ajustar el tamaño de redo sin considerar el tiempo operativo de reinicio.
Errores comunes: síntoma → causa raíz → solución
1) Síntoma: p99 de latencia de commit se dispara, el throughput parece correcto
Causa raíz: variación de latencia de fsync de redo (firmware del dispositivo, journaling del sistema de ficheros, congestión de writeback del kernel o contención de E/S).
Solución: mide eventos de espera de redo; revisa iostat -x por picos de write await; aísla E/S; valida opciones de sistema de ficheros; evita escritores competidores en el mismo volumen.
2) Síntoma: “tormentas de stalls” periódicas cada pocos minutos
Causa raíz: presión de checkpoint y flushing explosivo debido a redo subdimensionado y/o innodb_io_capacity bajo.
Solución: aumenta la capacidad de redo; sube innodb_io_capacity gradualmente; monitoriza páginas sucias y edad de checkpoint para estabilidad en lugar de patrones en sierra.
3) Síntoma: las lecturas se ralentizan cuando las escrituras aumentan, aunque NVMe no esté al máximo de throughput
Causa raíz: contención de colas de E/S por flushing en segundo plano o amplificación del doublewrite; lecturas atrapadas detrás de escrituras.
Solución: ajusta innodb_io_capacity y innodb_io_capacity_max; asegura que el scheduler sea apropiado; considera separar redo/binlog en dispositivos distintos solo si puedes manejar la complejidad operativa.
4) Síntoma: “log waits” aumentan bajo tráfico normal
Causa raíz: espacio de redo restringido; checkpoint no puede avanzar lo suficientemente rápido; presión del buffer de log; a veces redo demasiado pequeño.
Solución: aumenta la capacidad de redo; asegura que los page cleaners puedan flushar de forma constante; valida que doublewrite y sistema de ficheros no estén multiplicando E/S innecesariamente.
5) Síntoma: tras “hacerlo más rápido” cambiando durabilidad, la replicación/PITR falla
Causa raíz: postura de durabilidad inconsistente entre redo y binlog (p. ej., innodb_flush_log_at_trx_commit=2 pero sync_binlog=0), o suposiciones sobre consistencia tras crash.
Solución: alinea la durabilidad de redo y binlog con la tolerancia a pérdida de datos del negocio; documéntalo; prueba escenarios de fallo.
6) Síntoma: NVMe muestra %util alto, pero MySQL no hace tantas consultas
Causa raíz: flushing en segundo plano, doublewrite o writeback del sistema de ficheros; o otro proceso escribiendo mucho.
Solución: usa pidstat -d para encontrar escritores; revisa páginas sucias de InnoDB; revisa ajustes de writeback del kernel; considera mover cargas no BD fuera del volumen.
7) Síntoma: el ajuste funciona en staging pero falla en producción
Causa raíz: diferente modelo/firmware NVMe, distinto comportamiento de la nube, distinta concurrencia, o trabajos de fondo distintos (backups, compaction, ETL).
Solución: estandariza hardware; prueba con concurrencia realista; programa trabajos de fondo fuera de picos; mide latencia tail, no la media.
Listas de comprobación / plan paso a paso
Paso a paso: línea base antes de ajustar nada
- Registra versión de MySQL/MariaDB y variables clave: política de flush, dimensionamiento de redo, capacidad IO, doublewrite, durabilidad de binlog.
- Captura 10 minutos de
iostat -xdurante carga representativa. - Captura snapshot del status de InnoDB al inicio y al final de esa ventana (movimiento de checkpoint y LSN).
- Captura tendencias del porcentaje de páginas sucias.
- Confirma sistema de ficheros y opciones de montaje para el datadir.
- Confirma que no hay escritores pesados compitiendo en el mismo dispositivo.
Paso a paso: estabilizar redo y checkpoints
- Si la capacidad de redo es pequeña, aumenta para reducir el churn de checkpoints (mantenimiento planificado si es necesario).
- Tras el cambio, monitoriza el tiempo de recuperación durante reinicios controlados en staging; no despliegues un aumento de redo sin conocer el coste de reinicio.
- Observa
Innodb_log_waitsy la edad del checkpoint; deberían tender a bajar.
Paso a paso: ajustar IO capacity sin romper lecturas
- Aumenta
innodb_io_capacityen pasos pequeños. - Tras cada paso, observa: páginas sucias, latencia de lectura, write await y CPU.
- Configura
innodb_io_capacity_maxpara permitir ráfagas, pero mantenlo acotado. - Detente cuando las páginas sucias se estabilicen y la p99 de lectura no se deteriore.
Paso a paso: decidir sobre perillas de durabilidad como adulto
- Escribe la ventana aceptable de pérdida de datos (0 segundos? 1 segundo? más?). Haz que lo firme alguien que asuma la consecuencia.
- Alinea
innodb_flush_log_at_trx_commitysync_binlogcon esa decisión. - Prueba el comportamiento tras crash y la recuperación en staging: parada tipo pérdida de energía, luego recuperación y comprobaciones de consistencia.
Preguntas frecuentes
1) ¿Debo siempre poner innodb_flush_log_at_trx_commit=2 en NVMe?
No. NVMe puede hacer que fsync sea rápido, pero “rápido” no es lo mismo que “predecible”. Usa 2 solo si el negocio acepta perder hasta ~1 segundo de commits en un crash.
2) ¿Cuál es un buen tamaño de redo en NVMe?
Suficientemente grande para evitar presión constante de checkpoint, y lo bastante pequeño para mantener la recuperación dentro de tu presupuesto operativo. Empieza midiendo edad de checkpoint y tiempo de recuperación; no elijas un número de un blog.
3) ¿Aumentar la capacidad de redo siempre mejora el rendimiento?
Suele reducir stalls al suavizar checkpoints, pero puede aumentar el tiempo de recuperación tras fallo. Además, no arreglará una ruta de fsync fundamentalmente mala.
4) ¿Puedo desactivar el doublewrite en NVMe?
Sólo si puedes probar que toda tu pila evita páginas partidas (dispositivo + sistema de ficheros + configuración) y has probado la recuperación tras fallo. Si no, mantenlo activado y ajusta alrededor de él.
5) ¿Por qué sigue innodb_io_capacity en 200 en tantas configuraciones?
Porque las configuraciones viven más que las generaciones de hardware. 200 tenía sentido para discos giratorios. En NVMe puede ser generador de stalls.
6) Mi NVMe muestra 90% util pero bajo throughput. ¿Es normal?
Sí, si estás dominado por escrituras síncronas pequeñas (fsync) o E/S limitada por latencia. Alta util puede reflejar cola y tiempo de espera, no solo uso de ancho de banda.
7) ¿Vale la pena separar redo logs en otro dispositivo NVMe?
A veces, especialmente si las escrituras de flush de datos están asfixiando los fsyncs de redo. Pero añade complejidad operativa y puede fallar de maneras nuevas (planificación de capacidad, dominios de fallo de dispositivo). Mide primero.
8) MySQL vs MariaDB: ¿cuál es “mejor” en NVMe?
Ninguno gana por defecto. Elige según funciones, herramientas operativas y competencia del equipo. Luego afina usando las variables de ese motor y mide resultados.
9) ¿Por qué el ajuste en staging no coincide con producción?
Concurrencia, trabajos de fondo, vecinos ruidosos y firmware distinto del dispositivo. La latencia tail es deporte exclusivo de producción a menos que tu staging sea realmente parecido a producción.
Conclusión: próximos pasos que puedes hacer esta semana
Si quieres que NVMe parezca mágico en producción, no persigas IOPS máximas. Persigue latencia de fsync predecible y flushing constante.
El tamaño de redo es tu amortiguador. La política de flush es tu contrato de riesgo. La capacidad de E/S es cómo evitas que el motor pague sus deudas en pánico.
- Ejecuta las tareas de línea base: captura configuraciones de durabilidad, dimensionamiento de redo, páginas sucias, iostat, opciones de sistema de ficheros.
- Haz la secuencia de diagnóstico rápido durante un pico real y etiquétalo: fsync de redo, presión de checkpoint o contención de E/S.
- Si el redo es pequeño y el checkpoint es explosivo, planifica un aumento de capacidad de redo con prueba de tiempo de recuperación.
- Sube
innodb_io_capacitygradualmente hasta que las páginas sucias se estabilicen sin perjudicar p99 de lectura. - Anota las decisiones de durabilidad (
innodb_flush_log_at_trx_commit,sync_binlog) y deja de tratarlas como “perillas de rendimiento”.
Haz eso, y tu NVMe no solo será rápido. Será aburrido. Y aburrido es lo que quieres a las 2 a.m.