fio en ZFS para VMs: Perfiles que reflejan la realidad (no el marketing)

¿Te fue útil?

Tus usuarios de VM no abren tickets diciendo “los IOPS son bajos”. Dicen “la base de datos se congeló”, “las actualizaciones de Windows tardan una eternidad” o “ese job de CI está atascado en ‘copiando artefactos’ otra vez”.
Mientras tanto, tú ejecutas un fio rápido, obtienes cifras heroicas y todos se van a casa contentos—hasta el lunes.

ZFS facilita meter la pata más que la mayoría de los sistemas de archivos porque es honesto sobre durabilidad y tiene múltiples capas que pueden engañar (ARC, compresión, coalescencia de escrituras, grupos de transacciones).
La solución no es “ejecutar más fio”. La solución es ejecutar fio que se parezca al comportamiento real de las VMs: I/O mixto, semántica sync, profundidades de cola realistas y objetivos de latencia.

1) Comprobación de la realidad: cómo es realmente el I/O de las VMs

La mayoría de las cargas de almacenamiento de VMs no son “grandes escrituras secuenciales” ni “lecturas aleatorias 4K puras”.
Son un cóctel molesto:

  • Lecturas y escrituras aleatorias de bloques pequeños (4K a 64K) de bases de datos, metadatos, gestores de paquetes y servicios de fondo de Windows.
  • Escrituras sincrónicas en ráfaga (journals, WALs, tormentas de fsync, flushes desde guests).
  • Ratios mixtos lectura/escritura que cambian por horas (ventanas de backup, rotación de logs, patch Tuesdays).
  • Sensibilidad a la latencia más que a la capacidad máxima. Una VM puede “sentirse lenta” a 2.000 IOPS si p99 pasa de 2 ms a 80 ms.
  • La concurrencia es desigual: unas pocas VMs calientes pueden dominar, mientras la mayoría está tranquila pero aún requiere latencia de cola consistente.

Un perfil fio realista para VMs no busca maximizar un número titular. Busca medir los modos de fallo correctos:
latencia de escrituras sync, encolamiento, amplificación de escritura y si tu pool “rápido” se convierte en calabaza cuando TXG hace commit.

Si tu prueba no incluye fsync o un modo sync equivalente, no está midiendo el tipo de dolor que despierta a la gente a las 03:00.
Tu pool puede estar bien para ingest masivo y aún así ser terrible para VMs.

Broma #1: Si tus resultados de fio parecen demasiado buenos para ser reales, probablemente vienen del ARC, no de tus discos—como un currículum escrito por una capa de caché.

2) Hechos interesantes (y un poco de historia) que cambian cómo haces benchmarks

Esto no es trivia. Cada punto corresponde a un error de benchmark que he visto en producción.

  1. ZFS fue diseñado alrededor de copy-on-write y grupos de transacciones: las escrituras se agrupan y se confirman en lotes, lo que afecta los picos de latencia durante el sync de TXG.
  2. ARC (Adaptive Replacement Cache) es rendimiento basado en memoria: un ARC caliente puede hacer que pruebas de lectura con fio parezcan NVMe aunque el pool sea de discos giratorios.
  3. ZIL existe incluso si no tienes un SLOG: sin un dispositivo separado, el ZIL vive en el pool principal y compite con todo lo demás.
  4. SLOG acelera escrituras sync, no todas las escrituras: las escrituras async evitan el camino ZIL; probar sin semántica sync puede hacer que un SLOG parezca “inútil”.
  5. volblocksize se establece al crear el zvol: no lo “ajustas después” en un sentido práctico. Esto importa para la alineación de I/O de VM y la amplificación de escritura.
  6. recordsize es una propiedad de dataset, no de zvol: mezclar datasets y zvols esperando el mismo comportamiento es un error clásico de benchmark.
  7. La compresión puede aumentar los IOPS (a veces drásticamente) reduciendo escrituras físicas—hasta que la CPU se convierte en cuello de botella o los datos no comprimen.
  8. Los valores por defecto de fio pueden ser peligrosamente “amables”: I/O en buffer y profundidades de cola moderadas producen cifras que no sobrevivirán la concurrencia real de VMs.
  9. Las caches NVMe y la protección contra pérdida de energía importan: un NVMe “rápido” de consumo puede mentir bajo cargas sync si no tiene PLP adecuado.

3) La pila ZFS + I/O de VM: dónde tu benchmark te miente

El I/O de VM no es un único sistema. Es una cadena de decisiones y cachés. fio puede probar cualquier eslabón de esa cadena, y si pruebas el equivocado,
publicarás un benchmark para un sistema que en realidad no ejecutas.

Filesystem del guest vs disco virtual vs ZFS del host

El OS del guest tiene su propia caché y comportamiento de writeback. El hipervisor tiene su propio encolamiento. ZFS tiene ARC/L2ARC y su propia tubería de escritura.
Si ejecutas fio dentro del guest con I/O en buffer, estás midiendo principalmente memoria del guest y ancho de banda de memoria del host.
Si ejecutas fio en el host contra un archivo, estás midiendo datasets y comportamiento de recordsize, que puede no coincidir con el comportamiento de un zvol.

Semántica sync: donde vive el verdadero dolor

La diferencia definitoria entre “demo rápido” y “producción estable” es la durabilidad. Bases de datos y muchos filesystems de guest emiten flushes.
En ZFS, las escrituras síncronas pasan por la semántica ZIL; un dispositivo SLOG separado puede reducir la latencia proporcionando un objetivo de log de baja latencia.
Pero el SLOG no es magia: debe ser de baja latencia, consistente y seguro ante pérdida de energía.

El peor pecado con fio en benchmarking de VMs es ejecutar una prueba de escritura secuencial de 1M, ver 2–5 GB/s y declarar la plataforma “lista para bases de datos”.
Eso no es un perfil de VM. Eso es una diapositiva de marketing.

Profundidad de cola y paralelismo: iodepth no es una señal de virtud

Las cargas de VM a menudo tienen una iodepth moderada por VM pero alta concurrencia entre VMs. fio puede simular eso de dos formas:
numjobs (múltiples trabajadores independientes) y iodepth (profundidad de cola por trabajador).
Para VMs, prefiere más jobs con iodepth modesta en lugar de un job con iodepth=256 a menos que modeles específicamente una base de datos pesada.

Una forma fiable de crear rendimiento falso es elegir un iodepth que obligue al dispositivo a entrar en su mejor camino de merge secuencial.
Es como evaluar la conducción urbana de un coche dejándolo rodar cuesta abajo.

4) Principios para perfiles fio que coincidan con producción

Principio A: prueba lo que sienten los usuarios (latencia), no solo lo que venden los proveedores (throughput)

Captura latencia p95/p99, no solo el promedio. Tus clientes de VM viven en la cola.
fio puede reportar percentiles; úsalos y trátalos como métricas de primera clase.

Principio B: incluye pruebas de escrituras sync

Usa --direct=1 para evitar efectos de la page cache del guest (cuando pruebes dentro de guests) y usa un mecanismo sync:
--fsync=1 (o --fdatasync=1) para cargas basadas en archivos, o --sync=1 para algunos engines.
En dispositivos de bloque crudos, puedes aproximarlo usando --ioengine=libaio y forzando flushes con cuidado,
pero el modelo más limpio para “tormentas de flush” de VM es probar con un filesystem real + patrones de fsync.

Principio C: asegura que el working set derrote al ARC cuando quieras probar discos

Si quieres medir el rendimiento del pool, tu tamaño de prueba debería ser mucho mayor que el ARC.
Si ARC es 64 GiB, no ejecutes una lectura de 10 GiB y la llames “velocidad de disco”.
Alternativamente, prueba de forma que te enfoques en escrituras (especialmente sync) donde ARC no puede ocultar completamente el comportamiento físico.

Principio D: ajusta tamaños de bloque a lo que hacen los guests

El I/O aleatorio de VM tiende a agruparse en 4K, 8K, 16K y 32K. Bloques grandes de 1M son para streams de backup y cargas de medios.
Usa múltiples tamaños de bloque o una distribución si puedes. Si debes elegir uno: 4K aleatorio y 128K secuencial son los caballos de batalla.

Principio E: usa tests basados en tiempo con periodo de rampa

El comportamiento de ZFS cambia a medida que TXGs hacen commit, ARC se calienta, se crean metadatos y el espacio libre se fragmenta.
Ejecuta pruebas lo suficiente para ver algunos ciclos de TXG. Usa un periodo de rampa para evitar medir los primeros 10 segundos de “todo está vacío y feliz”.

Principio F: fija el entorno de prueba

Gobernador de CPU, balanceo de interrupciones, ajustes virtio, propiedades del dataset y del zvol importan.
La reproducibilidad es una característica. Si no puedes volver a ejecutar la prueba un mes después y explicar las diferencias, no estás haciendo benchmarking—estás midiendo sensaciones.

Una cita para pegar en una nota junto al panel de monitorización:
Todo falla, todo el tiempo. — Werner Vogels

5) Perfiles fio realistas (con explicaciones)

Estos no son perfiles “óptimos”. Son perfiles honestos. Úsalos como bloques de construcción y ajústalos a la mezcla de VMs que tengas.
Para cada perfil, decide si pruebas dentro del guest, en el host contra un zvol, o en el host contra un archivo en dataset.

Perfil 1: tormenta de arranque/inicio de sesión de VM (lectura-intenso, aleatorio pequeño, concurrencia moderada)

Modela docenas de VMs arrancando, servicios iniciándose, leyendo muchos archivos pequeños. Son mayormente lecturas, pero no puramente aleatorias.

cr0x@server:~$ fio --name=vm-boot --filename=/dev/zvol/tank/vm-101-disk0 \
  --rw=randread --bs=16k --iodepth=8 --numjobs=8 --direct=1 \
  --time_based --runtime=180 --ramp_time=30 --group_reporting \
  --ioengine=libaio --percentile_list=95,99,99.9
vm-boot: (groupid=0, jobs=8): err= 0: pid=21233: Sat Dec 21 11:02:20 2025
  read: IOPS=42.1k, BW=658MiB/s (690MB/s)(115GiB/180s)
    slat (usec): min=3, max=2100, avg=12.4, stdev=18.9
    clat (usec): min=90, max=28000, avg=1480, stdev=2100
     lat (usec): min=105, max=28150, avg=1492, stdev=2102
    clat percentiles (usec):
     | 95.00th=[ 3600], 99.00th=[ 8200], 99.90th=[18000]

Qué significa: 42k IOPS parece genial, pero la señal real son los p99 y p99.9 de latencia.
Las tormentas de arranque se sienten mal cuando p99 entra en decenas de milisegundos.
Decisión: si p99.9 es alto, busca contención (otras cargas), necesidades especiales de vdev, o vdevs demasiado pequeños/lentos.

Perfil 2: parecido a OLTP (mezcla aleatoria, las escrituras sync importan)

Este perfil expone si tu SLOG es real o de utilería.
Úsalo en un filesystem dentro del guest si puedes, porque los guests hacen fsync. En el host, puedes ejecutar contra un archivo en un dataset para modelar fsync.

cr0x@server:~$ fio --name=oltp-mix-fsync --directory=/tank/vmtest \
  --rw=randrw --rwmixread=70 --bs=8k --iodepth=4 --numjobs=16 \
  --direct=1 --time_based --runtime=300 --ramp_time=60 \
  --ioengine=libaio --fsync=1 --group_reporting --percentile_list=95,99,99.9
oltp-mix-fsync: (groupid=0, jobs=16): err= 0: pid=21901: Sat Dec 21 11:12:54 2025
  read: IOPS=18.4k, BW=144MiB/s (151MB/s)(42.2GiB/300s)
    clat (usec): min=120, max=95000, avg=2900, stdev=5200
    clat percentiles (usec):
     | 95.00th=[ 8200], 99.00th=[22000], 99.90th=[62000]
  write: IOPS=7.88k, BW=61.6MiB/s (64.6MB/s)(18.0GiB/300s)
    clat (usec): min=180, max=120000, avg=4100, stdev=7800
    clat percentiles (usec):
     | 95.00th=[12000], 99.00th=[34000], 99.90th=[90000]

Qué significa: Con fsync, las colas de latencia explotan primero. El promedio puede parecer “aceptable” mientras p99.9 arruina transacciones.
Decisión: si la latencia p99.9 de escritura es fea, valida SLOG, configuraciones de sync y el comportamiento de la cache de escritura del dispositivo.

Perfil 3: actualización de Windows / gestor de paquetes (metadata-intenso, lecturas/escrituras aleatorias pequeñas)

Aquí es donde vdevs especiales para metadatos y bloques pequeños pueden valer su coste—si realmente tienes el tipo correcto de pool.

cr0x@server:~$ fio --name=metadata-chaos --directory=/tank/vmtest \
  --rw=randrw --rwmixread=60 --bs=4k --iodepth=16 --numjobs=8 \
  --direct=1 --time_based --runtime=240 --ramp_time=30 \
  --ioengine=libaio --group_reporting --percentile_list=95,99,99.9
metadata-chaos: (groupid=0, jobs=8): err= 0: pid=22188: Sat Dec 21 11:18:22 2025
  read: IOPS=55.0k, BW=215MiB/s (226MB/s)(50.4GiB/240s)
    clat percentiles (usec): 95.00th=[ 2400], 99.00th=[ 6800], 99.90th=[16000]
  write: IOPS=36.0k, BW=141MiB/s (148MB/s)(33.0GiB/240s)
    clat percentiles (usec): 95.00th=[ 3100], 99.00th=[ 9200], 99.90th=[24000]

Qué significa: Si estos percentiles empeoran drásticamente cuando el pool está medio lleno o fragmentado,
puedes tener un problema de layout/ashift, un mirror vdev sobrecargado, o te faltan rutas rápidas para metadatos.
Decisión: compara rendimiento en distintos niveles de ocupación del pool y después de escrituras aleatorias sostenidas.

Perfil 4: flujo de backup/restore (secuencial, bloques grandes, comprueba “¿podemos drenar?”)

Este perfil no es una prueba de latencia para VMs. Responde: “¿Podemos mover datos grandes sin destruir todo?”
Úsalo para programar ventanas de backup y decidir si debes limitar la tasa.

cr0x@server:~$ fio --name=backup-write --filename=/tank/vmtest/backup.bin \
  --rw=write --bs=1m --iodepth=8 --numjobs=1 --direct=1 \
  --size=50G --ioengine=libaio --group_reporting
backup-write: (groupid=0, jobs=1): err= 0: pid=22502: Sat Dec 21 11:24:10 2025
  write: IOPS=1450, BW=1450MiB/s (1520MB/s)(50.0GiB/35s)

Qué significa: Gran throughput no significa que tu pool sea sano para VMs.
Decisión: usa esto para fijar límites de backup; luego vuelve a ejecutar un perfil sensible a latencia en concurrencia para ver la interferencia.

Perfil 5: prueba de disco “sin trampas” (working set mayor que ARC, lecturas aleatorias)

Úsalo cuando alguien afirme que el pool “es lento” y necesitas establecer la capacidad real de lectura sin que ARC enmascare la verdad.
Debes dimensionar el archivo por encima del ARC y ejecutar tiempo suficiente para evitar artefactos de cache caliente.

cr0x@server:~$ fio --name=arc-buster --filename=/tank/vmtest/arc-buster.bin \
  --rw=randread --bs=128k --iodepth=32 --numjobs=4 --direct=1 \
  --size=500G --time_based --runtime=240 --ramp_time=30 \
  --ioengine=libaio --group_reporting --percentile_list=95,99
arc-buster: (groupid=0, jobs=4): err= 0: pid=22791: Sat Dec 21 11:31:12 2025
  read: IOPS=3100, BW=387MiB/s (406MB/s)(90.7GiB/240s)
    clat percentiles (usec):
     | 95.00th=[ 16000], 99.00th=[ 32000]

Qué significa: IOPS más bajos y mayor latencia son normales aquí; por fin estás tocando discos.
Decisión: si esto es inesperadamente terrible, revisa layout de vdev, ashift y salud de discos antes de discutir flags de fio.

6) Tareas prácticas: comandos, qué significa la salida y qué decides

Esta es la parte que realmente usarás durante un incidente o una revisión de capacidad.
Cada tarea incluye: un comando, salida de muestra, qué significa y la decisión que impulsa.
Asume un host Linux con ZFS y un pool llamado tank.

Tarea 1: Identificar si estás midiendo un zvol o un dataset (y qué propiedades aplican)

cr0x@server:~$ zfs list -o name,type,volblocksize,recordsize,compression,sync tank
NAME                     TYPE     VOLBLOCKSIZE  RECORDSIZE  COMPRESS  SYNC
tank                     filesystem       -        128K     lz4       standard
tank/vmdata              filesystem       -        128K     lz4       standard
tank/vm-101-disk0         volume        16K          -      lz4       standard

Qué significa: los zvols tienen volblocksize; los datasets tienen recordsize.
Mezclar sus resultados es como “optimizar” accidentalmente lo equivocado.
Decisión: elige el objetivo de fio en consecuencia: /dev/zvol/... para discos VM en zvol, o un archivo en el dataset si tu almacenamiento de VM usa archivos.

Tarea 2: Comprobar la topología del pool (tu layout de vdev es tu contrato de rendimiento)

cr0x@server:~$ zpool status -v tank
  pool: tank
 state: ONLINE
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          mirror-0                  ONLINE       0     0     0
            nvme0n1                 ONLINE       0     0     0
            nvme1n1                 ONLINE       0     0     0
          mirror-1                  ONLINE       0     0     0
            nvme2n1                 ONLINE       0     0     0
            nvme3n1                 ONLINE       0     0     0
        logs
          nvme4n1                   ONLINE       0     0     0

errors: No known data errors

Qué significa: Los mirrors se comportan diferente a RAIDZ bajo I/O aleatorio. logs indica que existe un dispositivo SLOG separado.
Decisión: si estás probando escrituras sync, verifica que logs están presentes y sanos; si es RAIDZ, espera IOPS menores para escrituras aleatorias pequeñas y planifica en consecuencia.

Tarea 3: Comprobar espacio libre del pool y señales de riesgo de fragmentación

cr0x@server:~$ zfs list -o name,used,avail,refer,mountpoint tank
NAME         USED  AVAIL  REFER  MOUNTPOINT
tank        42.8T  6.10T   192K  /tank

Qué significa: Pools con poco espacio tienden a mostrar peor comportamiento de asignación y peor latencia de cola.
ZFS no es especialmente peor aquí; solo es honesto sobre las consecuencias.
Decisión: si avail está justo, deja de “benchmarkear” y comienza trabajo de capacidad. Cualquier prueba fio ahora está midiendo un sistema ya en tensión.

Tarea 4: Validar que las opciones sync no te estén mintiendo

cr0x@server:~$ zfs get -r -o name,property,value sync tank/vmdata
NAME        PROPERTY  VALUE
tank/vmdata sync      standard

Qué significa: sync=standard significa que las solicitudes sync son respetadas. sync=disabled hace los benchmarks bonitos y a las auditorías enojadas.
Decisión: si alguien puso sync=disabled “temporalmente”, trata todos los resultados de rendimiento como contaminados.

Tarea 5: Verificar ashift (porque los discos 4K no perdonan fantasías de 512 bytes)

cr0x@server:~$ zdb -C tank | grep -E "ashift|vdev_tree" -n | head
120:        ashift: 12

Qué significa: ashift=12 indica sectores de 4K. Un ashift incorrecto puede degradar permanentemente el rendimiento vía read-modify-write.
Decisión: si ashift es incorrecto, planifica una migración. No te “tunarás” fuera de eso.

Tarea 6: Comprobar tamaño de ARC vs tamaño de la prueba (¿estás midiendo RAM?)

cr0x@server:~$ arcstat 1 1
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
11:40:22  128K   56K     43   10K   8%   40K  31%    6K   4%   64G   80G

Qué significa: ARC es 64G con target 80G. Si tu archivo fio es menor que eso, las lecturas “mejorarán” con el tiempo.
Decisión: para pruebas de disco, usa un archivo varias veces mayor que ARC, o enfócate en escrituras sync donde ARC no puede ocultar totalmente la latencia.

Tarea 7: Vigila I/O y latencia de ZFS a nivel de pool durante fio

cr0x@server:~$ zpool iostat -v tank 1 3
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                        42.8T  6.10T  8.20K  3.10K   410M   220M
  mirror-0                  21.4T  3.05T  4.10K  1.55K   205M   110M
    nvme0n1                      -      -  2.05K    780   102M    55M
    nvme1n1                      -      -  2.05K    770   103M    55M
  mirror-1                  21.4T  3.05T  4.10K  1.55K   205M   110M
    nvme2n1                      -      -  2.04K    780   102M    55M
    nvme3n1                      -      -  2.06K    770   103M    55M
--------------------------  -----  -----  -----  -----  -----  -----

Qué significa: Ves si la carga se reparte entre vdevs o si un lado está caliente.
Decisión: si un vdev está sobrecargado (o un disco es más lento), investiga desequilibrio, firmware o un dispositivo fallando.

Tarea 8: Confirmar que el SLOG realmente se usa para escrituras sync

cr0x@server:~$ zpool iostat -v tank 1 2 | sed -n '1,18p'
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                        42.8T  6.10T  2.10K  6.40K   120M   210M
  mirror-0                  21.4T  3.05T  1.05K  3.10K    60M   105M
  mirror-1                  21.4T  3.05T  1.05K  3.30K    60M   105M
logs                             -      -     2  9.80K   512K   310M
  nvme4n1                         -      -     2  9.80K   512K   310M

Qué significa: Altas operaciones de escritura en el dispositivo de log durante un fio sync-intenso indican que el tráfico ZIL está aterrizando en SLOG.
Decisión: si las escrituras de log no se mueven durante pruebas sync, o la carga no es sync, sync=disabled está activo en algún lado, o SLOG no está configurado.

Tarea 9: Revisar cuellos de botella de CPU y presión de IRQ durante benchmarks “rápidos”

cr0x@server:~$ mpstat -P ALL 1 2
Linux 6.8.0 (server)  12/21/2025  _x86_64_  (32 CPU)

12:01:10 PM  CPU   %usr  %nice   %sys %iowait  %irq  %soft  %idle
12:01:11 PM  all   18.2   0.0   22.9    0.8    0.0   6.1   52.0
12:01:11 PM   7    4.0   0.0   78.0    0.0    0.0   0.0   18.0

Qué significa: Una CPU a tope en %sys puede indicar un cuello de botella en una sola cola/IRQ (NVMe, virtio, networking o locking).
Decisión: si estás limitado por CPU, deja de tunear discos. Arregla el encolamiento, afinidad de IRQ o aumenta paralelismo.

Tarea 10: Verificar comportamiento de flush del guest (por qué aparece “sync” todo el tiempo)

cr0x@server:~$ qemu-img info /tank/vmimages/vm-101.qcow2
image: /tank/vmimages/vm-101.qcow2
file format: qcow2
virtual size: 200 GiB (214748364800 bytes)
disk size: 36.1 GiB
cluster_size: 65536

Qué significa: qcow2 tiene su propia metadata y comportamiento de asignación. Puede amplificar escrituras e introducir flushes extra según la configuración.
Decisión: si la latencia es mala bajo cargas sync, considera raw/zvol o ajusta cuidadosamente cache/IO mode de qcow2 (y prueba de nuevo).

Tarea 11: Comprobar ratio de compresión de ZFS y decidir si la CPU ayuda o perjudica

cr0x@server:~$ zfs get -o name,property,value,source compressratio,compression tank/vmdata
NAME        PROPERTY       VALUE  SOURCE
tank/vmdata compression    lz4    local
tank/vmdata compressratio  1.62x  -

Qué significa: Un compressratio real sugiere que tu pool escribe menos en disco de lo que la VM cree.
Decisión: si compressratio es alto y la CPU no está saturada, la compresión es ganancia neta. Si la CPU está al límite, prueba con y sin compresión.

Tarea 12: Verificar expectativas de alineación de bloques del zvol para I/O de VM

cr0x@server:~$ lsblk -o NAME,PHY-SEC,LOG-SEC,MIN-IO,OPT-IO,ROTA /dev/zvol/tank/vm-101-disk0
NAME                     PHY-SEC LOG-SEC MIN-IO OPT-IO ROTA
zd0                        4096    4096   4096      0    0

Qué significa: Sectores lógicos/físicos de 4K concuerdan con expectativas modernas. Desalineación causa RMW y picos de latencia.
Decisión: si ves sectores lógicos 512 sobre dispositivos 4K, arréglalo en diseño (ashift/volblocksize). Si no, seguirás “tuneando” para siempre.

Tarea 13: Medir señales de presión de sync de TXG

cr0x@server:~$ cat /proc/spl/kstat/zfs/txgs
1 0x01 0x00000000 136 13440 105155148830 0

Qué significa: Este archivo puede cambiar según la implementación, pero si los tiempos de sync de TXG o el backlog crecen durante carga, verás olas de latencia.
Decisión: si la latencia de cola se correlaciona con el comportamiento de sync de TXG, investiga límites de datos sucios, latencia de vdev y eficacia del SLOG en lugar de perseguir flags de fio.

Tarea 14: Comprobar contadores de error de dispositivo y outliers de latencia antes de culpar a ZFS

cr0x@server:~$ smartctl -a /dev/nvme0n1 | sed -n '1,25p'
smartctl 7.4 2023-08-01 r5530 [x86_64-linux-6.8.0] (local build)
=== START OF INFORMATION SECTION ===
Model Number:                       ACME NVMe 3.2TB
Firmware Version:                   1.04
Percentage Used:                    2%
Data Units Read:                    19,442,112
Data Units Written:                 13,188,440
Media and Data Integrity Errors:    0
Error Information Log Entries:      0

Qué significa: Un único dispositivo inestable puede convertir p99 en una historia de terror mientras el promedio parece decente.
Decisión: si aparecen errores o alto desgaste, reemplaza el dispositivo antes de “optimizar” alrededor de hardware que está fallando.

7) Guion de diagnóstico rápido: encuentra el cuello de botella en minutos

Este es el playbook “deja de debatir, empieza a aislar”. Úsalo cuando la latencia sea alta o los resultados de fio no coincidan con producción.
El objetivo es identificar si estás limitado por el guest, el hipervisor, ZFS, el layout de vdev o un único dispositivo enfermo.

Primero: decide qué estás realmente probando

  • fio en guest, I/O en buffer → mayormente comportamiento de caché y memoria del guest.
  • fio en guest, I/O directo → más cercano al comportamiento del disco virtual (aún pasa por colas del hipervisor).
  • fio en host contra un zvol → prueba la ruta de volumen de bloque de ZFS, evita el filesystem del guest.
  • fio en host contra un archivo en dataset → prueba la ruta de dataset de ZFS y comportamiento de recordsize.

Si el objetivo de la prueba no coincide con la ruta de almacenamiento de las VMs, para. Arregla la prueba.

Segundo: pregunta “¿es sync?”

  • Ejecuta un perfil fio intensivo en sync (--fsync=1 o equivalente) y observa zpool iostat -v por actividad en logs.
  • Comprueba zfs get sync en el nivel dataset/zvol.

Si la latencia p99 de escritura explota solo con sync, tu problema está en ZIL/SLOG, seguridad del write cache del dispositivo o latencia de escritura en los vdevs.

Tercero: determina si ARC está enmascarando lecturas

  • Compara una prueba de lectura que rompa ARC con una prueba de lectura pequeña.
  • Observa tasas de miss en arcstat durante la ejecución.

Si las “lecturas de disco” son rápidas pero los misses son bajos, no estás leyendo discos. Estás leyendo RAM y llamándolo almacenamiento.

Cuarto: localiza el punto de estrangulamiento con estadísticas en vivo

  • zpool iostat -v 1 muestra la distribución por vdev y si se usan logs.
  • mpstat 1 muestra saturación de CPU y presión en un solo núcleo.
  • iostat -x 1 muestra utilización de dispositivo y latencia en el nivel de bloque.

Si un solo dispositivo está saturado o muestra await alto, aíslalo. Si la CPU está saturada, deja de comprar SSDs más rápidos.

Quinto: comprueba salud del pool y realidad de asignación

  • Pool casi lleno? Espera peor comportamiento.
  • ¿Resilver reciente? ¿Scrub en curso? Espera interferencia.
  • ¿Errores? Deja el trabajo de rendimiento y arregla la integridad primero.

8) Errores comunes: síntomas → causa raíz → solución

Error 1: “fio muestra 1M IOPS pero las VMs son lentas”

Síntomas: Lecturas masivas en fio, pero aplicaciones reales tienen latencia alta y bloqueos.

Causa raíz: Benchmark en ARC/page cache. El archivo de prueba cabe en RAM; fio está leyendo caché, no almacenamiento.

Solución: Usa --direct=1, haz el working set mayor que el ARC y vigila el %miss de arcstat durante la ejecución.

Error 2: “El SLOG no hizo nada”

Síntomas: Añadir un SLOG no muestra mejora; latencia de escrituras sync sin cambios.

Causa raíz: La carga no era sync (sin fsync/flush), o sync=disabled está establecido, o el dispositivo de log no está activo.

Solución: Ejecuta fio intensivo en fsync, verifica zpool status para ver logs y confirma operaciones de escritura de log en zpool iostat -v.

Error 3: “Aumentamos iodepth y obtuvimos mejores números, así que listo”

Síntomas: IOPS de benchmark mejoraron con iodepth=256; en producción sigue habiendo problemas.

Causa raíz: Encolamiento artificial que oculta latencia. Estás midiendo throughput de saturación, no tiempo de servicio.

Solución: Usa valores de iodepth que coincidan con comportamiento de VMs (a menudo 1–16 por job) y sigue p99/p99.9 de latencia.

Error 4: “Las escrituras aleatorias son terribles; ZFS es lento”

Síntomas: Pruebas de escrituras aleatorias pequeñas son malas, especialmente en RAIDZ.

Causa raíz: Sobrecoste de paridad de RAIDZ más costo de COW bajo escrituras aleatorias pequeñas. Es física esperada.

Solución: Para I/O aleatorio intenso en VMs, usa mirrors (o diseños de vdev especiales) y dimensiona número de vdevs por IOPS, no por capacidad bruta.

Error 5: “Picos de latencia cada tanto como un latido”

Síntomas: p99 salta periódicamente durante carga constante.

Causa raíz: Comportamiento de sync de TXG, throttling por datos sucios, o un dispositivo lento que crea paradas periódicas.

Solución: Correlaciona picos con estadísticas de ZFS y await de disco; valida firmware de dispositivo y considera mejoras de latencia de escritura (mejores vdevs, mejor SLOG).

Error 6: “Tuvimos que tunear recordsize para discos VM”

Síntomas: Cambios en recordsize no muestran efecto en rendimiento de zvols.

Causa raíz: recordsize no aplica a zvols; aplica volblocksize.

Solución: Crea zvols con volblocksize apropiado desde el inicio; migra si es necesario.

Error 7: “La compresión lo hizo más rápido en fio, así que será mejor”

Síntomas: IOPS suben con compresión activada; la CPU sube; bajo carga real, la latencia empeora.

Causa raíz: Cuello de botella de CPU o datos no compresibles. La compresión ayuda, pero no es gratis.

Solución: Mide la holgura de CPU durante concurrencia realista; revisa compressratio; mantén compresión si reduce escrituras sin saturar CPUs.

Broma #2: Cambiar sync=disabled para “arreglar rendimiento” es como quitar el detector de humo porque te sigue despertando.

9) Tres micro-historias corporativas desde las trincheras

Historia A: Un incidente causado por una suposición errónea (caché ≠ disco)

Una compañía SaaS mediana desplegó un nuevo clúster de VMs para CI interna y un par de bases de datos orientadas a clientes.
El almacenamiento era ZFS sobre buenos NVMe en mirrors. La prueba de preparación fue un fio que mostró IOPS de lectura absurdas.
Todos se relajaron. Compras obtuvo una estrella dorada.

Dos semanas después, el canal de incidentes se encendió: picos de latencia en bases de datos, runners de CI con timeouts, advertencias aleatorias de “tareas colgadas” en kernels de guests.
El on-call ejecutó el mismo job de fio y otra vez obtuvo las cifras grandes. Esto creó un tipo especial de miseria: cuando las métricas dicen “rápido”
pero las personas dicen “lento”, pierdes horas discutiendo qué realidad cuenta.

La suposición errónea fue simple: “IOPS de lectura de fio = rendimiento de disco.” El archivo de prueba era pequeño.
El ARC era enorme. Bajo carga estable de VMs, el working set caliente no era estable y las escrituras sync estaban empujando el comportamiento de TXG hacia olas de latencia visibles.
fio estaba haciendo benchmarking de memoria.

La solución no fue exótica. Reconstruyeron la suite fio: pruebas basadas en tiempo, archivos mucho mayores que ARC y una carga mixta con fsync.
Los números empeoraron, que fue lo mejor que les pasó—ahora coincidían con producción. Luego encontraron un NVMe con latencia de escritura inconsistente.
Reemplazarlo estabilizó p99.9 y mágicamente “mejoró la app”, que es el único benchmark que realmente importa.

Historia B: Una optimización que salió mal (el atajo sync)

Una plataforma financiera tenía un granja de VMs ejecutando un message bus y un par de clusters PostgreSQL.
Durante un ensayo de pico, vieron latencia de commit elevada. Alguien sugirió un cambio “temporal” en ZFS:
poner sync=disabled en el dataset que alojaba discos VM para hacer commits más rápidos.

Funcionó de inmediato. Las gráficas de latencia bajaron. El ensayo pasó. El cambio se quedó.
El equipo no fue temerario; estaban ocupados y la plataforma no tenía cultura de revisión de deriva de configuración.
Meses después, un evento de pérdida de energía afectó un rack. Los hosts rebootearon limpios. Las VMs volvieron. Algunos servicios no.

Lo que siguió fue una semana de trabajo forense que nadie disfruta: patrones sutiles de corrupción en bases de datos, mensajes reconocidos perdidos y una lenta reconstrucción de la confianza.
No hubo una línea de log cazadora. Rara vez la hay. La “optimización” convirtió la durabilidad de un contrato en una sugerencia.
ZFS hizo lo que se le indicó. El sistema falló exactamente como estaba configurado.

El efecto colateral no fue solo la caída. Fue la deuda operativa a largo plazo:
tuvieron que auditar cada dataset, re-baselinear rendimiento con sync activado, validar hardware de SLOG y re-entrenar equipos para tratar configuraciones de durabilidad como controles de seguridad en producción.
La solución de rendimiento eventual involucró mejores dispositivos de log y más mirrors—no mentirle a la pila de almacenamiento.

Historia C: Una práctica aburrida pero correcta que salvó el día (baselines reproducibles)

Otra organización—esta con un proceso de cambios dolorosamente maduro—mantenía una pequeña suite de perfiles fio versionada junto a su código de infraestructura.
Mismas versiones de fio. Mismos jobfiles. Mismo runtime. Mismos datasets objetivo. Cada cambio de almacenamiento requería una ejecución y un informe adjunto.
A nadie le encantaba. No era glamoroso.

Un trimestre, cambiaron la versión de firmware de un HBA durante una ventana de mantenimiento. Nada más cambió.
Al día siguiente, unas cuantas VMs empezaron a reportar stalls ocasionales. No suficientes para un incidente completo, solo lo justo para inquietar a la gente.
El equipo ejecutó su suite fio estándar y la comparó con la baseline del mes anterior. La latencia p99 de escritura estaba significativamente peor en perfiles sync-intensos.

Como la suite baseline ya existía, no debatieron metodología. No perdieron tiempo con iodepth.
Tenían un “sentir del sistema” conocido capturado en números que importan.
Revirtieron el firmware y las señales de stall desaparecieron.

Lo que salvó el día fue aburrido: pruebas controladas y reproducibles con percentiles de latencia y semántica sync.
Les permitió tratar el rendimiento como un problema de regresión, no como una discusión filosófica.

10) Listas de verificación / plan paso a paso

Paso a paso: construir una suite fio con realidad de VM para ZFS

  1. Haz inventario de la ruta de almacenamiento de tus VMs. ¿Los discos VM son zvols, archivos raw, qcow2 u otra cosa?
  2. Captura propiedades ZFS para los datasets/zvols relevantes: compression, sync, recordsize/volblocksize.
  3. Elige tres perfiles centrales:
    • 4K/8K mixto aleatorio con fsync (enfocado en latencia)
    • 16K tormenta de lectura aleatoria (comportamiento de arranque/login)
    • 1M escritura secuencial (throughput de backup/restore)
  4. Decide la estructura de jobs: prefiere numjobs para concurrencia y mantén iodepth moderado.
  5. Usa ejecuciones basadas en tiempo (3–10 minutos) con periodo de rampa (30–60 segundos).
  6. Mide percentiles (95/99/99.9) y trata p99.9 como el “proxy del dolor del usuario”.
  7. Dimensiona archivos de prueba para exceder ARC si pretendes medir lecturas de disco.
  8. Ejecuta pruebas en tres modos:
    • Host → zvol
    • Host → archivo en dataset
    • Guest → filesystem (direct I/O y fsync)
  9. Registra el entorno: versiones de kernel/ZFS, gobernador de CPU, topología del pool y si hay scrubs/resilvers en curso.
  10. Repite al menos dos veces y compara. Si los resultados varían mucho, esa variabilidad es en sí misma el hallazgo.

Lista operativa: antes de confiar en cualquier número de fio

  • ¿Se usó --direct=1 cuando debía?
  • ¿El perfil incluye fsync/flush al modelar bases de datos o durabilidad de VM?
  • ¿El archivo de prueba es más grande que ARC (para tests de lectura)?
  • ¿Estás rastreando latencia p99/p99.9?
  • ¿Vigilas zpool iostat -v y CPU durante la prueba?
  • ¿El pool está sano (sin errores, sin vdevs degradados)?
  • ¿El pool no está casi lleno?
  • ¿Ejecutaste la prueba en la ruta de almacenamiento real usada por las VMs?

Lista de cambios: al tunear ZFS para cargas VM

  • No toques la durabilidad primero. Deja sync tal cual a menos que disfrutes retros de incidentes.
  • Prefiere decisiones de layout sobre micro-tuning. Mirrors vs RAIDZ es una elección de diseño, no un sysctl.
  • Valida SLOG con fio intensivo en sync y confirma que se usa.
  • Alinea volblocksize a la realidad del guest al crear zvols.
  • Mide riesgo de regresión con una suite baseline después de cada cambio significativo.

11) Preguntas frecuentes

Q1: ¿Debo ejecutar fio dentro de la VM o en el host?

Ambos, pero por razones distintas. Dentro de la VM te dice lo que experimenta el guest (incluyendo colas del hipervisor y comportamiento del filesystem del guest).
En el host aísla el comportamiento de ZFS. Si discrepan, eso es una pista: el cuello de botella está en la capa de virtualización o en caching.

Q2: ¿Qué flags de fio importan más para realismo de VM?

--direct=1, tamaños de bloque realistas --bs, --iodepth moderado, múltiples --numjobs, ejecuciones basadas en tiempo,
y --fsync=1 (o equivalente) para cargas sensibles a durabilidad. También: --percentile_list para dejar de mirar solo promedios.

Q3: ¿Por qué mi prueba de lectura aleatoria se hace más rápida con el tiempo?

ARC (o page cache del guest) se está calentando. Pasas de disco a memoria. Si tratas de probar discos, aumenta el working set y observa misses en ARC.

Q4: ¿Cómo sé si mi SLOG está ayudando?

Ejecuta un perfil fio intensivo en sync y observa operaciones de escritura en el dispositivo log con zpool iostat -v. También compara latencia p99 de escritura con y sin SLOG.
Si tu carga no es sync, SLOG no debería ayudar—y eso no es un fallo.

Q5: ¿RAIDZ es “malo” para almacenamiento de VMs?

RAIDZ no es malo; simplemente no es una bestia de IOPS para escrituras aleatorias pequeñas. Para cargas tipo OLTP de VMs, los mirrors suelen ser la opción más segura.
Si necesitas RAIDZ por eficiencia de capacidad, planifica la realidad de rendimiento y prueba con escrituras random + sync.

Q6: ¿Debo cambiar recordsize para rendimiento de VMs?

Solo para datasets usados como archivos (como qcow2/raw). Para discos respaldados por zvol, recordsize no aplica; aplica volblocksize.

Q7: ¿Cuál es un buen objetivo para latencia p99?

Depende de la carga, pero como regla: si p99 de escritura sync entra regularmente en decenas de milisegundos, las bases de datos se quejarán.
Usa los SLOs de tu app para fijar un umbral; luego ajusta diseño (vdevs, SLOG) para alcanzarlo.

Q8: ¿Cómo evito que fio destruya el rendimiento del pool para todos los demás?

Ejecuta en ventanas de mantenimiento, limita con menos jobs/iodepth y monitorea. fio es un generador de carga, no un invitado educado.
Si debes probar en producción, usa ejecuciones más cortas y prioriza perfiles de latencia sobre saturar el throughput.

Q9: ¿Activar compresión siempre ayuda cargas VM?

A menudo ayuda, porque datos de VM (archivos OS, logs) pueden comprimirse y reducir escrituras físicas. Pero si la CPU se convierte en cuello de botella o los datos no comprimen,
la compresión puede perjudicar la latencia de cola. Revisa compressratio y CPU durante carga realista.

Q10: ¿Por qué mis resultados de fio difieren entre zvols y archivos en dataset?

Caminos de código y propiedades diferentes. Los datasets usan recordsize y metadata de archivos; los zvols usan volblocksize y presentan un dispositivo de bloque.
Las plataformas VM también se comportan distinto según uses archivos raw, qcow2 o zvols.

12) Pasos prácticos siguientes

Si quieres que tus resultados de fio predigan la realidad de VMs, haz lo siguiente, en este orden:

  1. Elige un disco VM (zvol o archivo) y crea tres perfiles fio: tormenta de arranque, OLTP mixto con fsync, flujo de backup.
  2. Ejecuta basados en tiempo con percentiles, y registra p95/p99/p99.9, no solo IOPS.
  3. Durante cada ejecución, captura zpool iostat -v, arcstat y estadísticas de CPU.
  4. Valida la ruta sync: confirma actividad en SLOG (si existe) y verifica que ningún dataset tenga sync=disabled ocultando problemas.
  5. Convierte los resultados en una baseline y vuelve a ejecutar después de cada cambio importante: firmware, kernel, versión de ZFS, topología y formato de almacenamiento de VMs.

El objetivo no es obtener números bonitos. El objetivo es dejar de sorprenderte por producción.
Una vez que tu suite fio hace doler las mismas cosas que se quejan los usuarios, por fin estás haciendo benchmarking del sistema que realmente operas.

← Anterior
ZFS xattr: la elección de compatibilidad que altera el rendimiento
Siguiente →
Titulares de 0-day: por qué una vulnerabilidad puede causar pánico instantáneo

Deja un comentario