ZFS IOPS vs Throughput: Deja de leer la métrica equivocada

¿Te fue útil?

Tu almacenamiento “parece estar bien”. El panel muestra 2 GB/s de lectura, el pool está en verde, y sin embargo la base de datos jadea como si acabara de correr un maratón con un abrigo de lana.
Todos discuten de todos modos. Alguien señala el “ancho de banda del disco” y declara victoria. Mientras tanto, tu gráfica de latencia p95 está haciendo incendios silenciosamente.

Los problemas de rendimiento en ZFS raramente empiezan por un disco roto. Empiezan por un modelo mental roto: leer la métrica equivocada para la carga de trabajo que realmente tienes.
Esta es la diferencia entre “estamos limitados por IOPS” y “estamos limitados por throughput”, y decide si compras más discos, cambias recordsize, añades un vdev especial o simplemente dejas de usar benchmarks engañosos.

La discordancia de métricas: por qué 2 GB/s aún puede ser lento

El throughput es seductoramente simple. Es un número grande. Se ve bien en una diapositiva. También es la métrica más fácil de usar incorrectamente.
Un sistema puede mover gigabytes por segundo y aun así ofrecer una experiencia de usuario terrible si hace ese trabajo en grandes flujos secuenciales felices mientras tu carga real son lecturas aleatorias pequeñas con presupuestos de latencia estrictos.

La relación central es esta:
Throughput = IOPS × tamaño de IO.
Esto no es un eslogan; es la matemática detrás de la mayoría de las discusiones sobre almacenamiento.
Si tu tamaño de IO es 4 KiB y puedes hacer 20,000 IOPS, eso son unos 80 MiB/s. No impresionante, pero puede ser exactamente lo que necesita una base de datos.
Si tu tamaño de IO es 1 MiB y puedes hacer 2,000 IOPS, eso son 2 GiB/s. Impresionante, y completamente irrelevante para esa misma base de datos.

En el mundo ZFS, esta discordancia empeora porque la pila es honesta pero complicada:
ZFS tiene compresión, sumas de verificación, copy-on-write, una gran caché ARC, prefetching y transaction groups.
Estas funciones no son “lentas”. Simplemente son específicas. Recompensan algunos patrones de IO y penalizan otros.

Si recuerdas solo una cosa, que sea esta:
Deja de usar una sola gráfica de “MB/s” para decidir la salud del almacenamiento.
Cuando la latencia es el síntoma visible por el usuario, mide la latencia primero. Luego mapea esa latencia a IOPS vs throughput y al verdadero cuello de botella (CPU, colas de vdev, ruta sync, fragmentación, metadata, red o la app).

Broma #1: Si mediste tu pool con una sola prueba de lectura secuencial y lo declaraste “rápido”, felicidades: has medido tu capacidad para leer el archivo del benchmark.

IOPS, throughput, latencia: los tres números que importan (y cómo se relacionan)

IOPS

IOPS son “operaciones de IO por segundo”. Cuenta las solicitudes de IO completadas, no los bytes.
Importa más cuando la aplicación hace muchas lecturas/escrituras pequeñas: bases de datos, discos de VM, cargas con mucho metadata, buzones de correo, cachés CI de archivos pequeños.

IOPS no son gratis. Cada IO tiene sobrecarga: syscall, contabilidad del sistema de archivos, checksum, asignación, planificación del vdev, acceso al medio, finalización.
Tu pool puede tener alto throughput y bajas IOPS porque es bueno en transferencias secuenciales grandes pero malo en muchas operaciones aleatorias pequeñas.

Throughput

Throughput (MB/s o GB/s) es total de bytes movidos por segundo. Importa más para streaming: backups, procesamiento de medios, ETL de archivos grandes, replicación de object storage.
Para IO secuencial, una sola cola con suficiente profundidad de solicitudes pendientes puede saturar el throughput con sorprendentemente pocas IOPS.

Latencia

Latencia es tiempo por IO (promedio, p95, p99). Usualmente es la métrica que las personas experimentan.
Una base de datos que espera 15 ms por lectura no se preocupa de que tu pool pueda alcanzar 3 GB/s si pidió 8 KiB y lo recibió tarde.

En términos prácticos: la latencia te dice si estás en problemas; IOPS/throughput te dicen por qué.

La relación que sigues ignorando

Si el tamaño de IO es pequeño, necesitas muchas IOPS para obtener buen throughput.
Si el tamaño de IO es grande, puedes obtener alto throughput con IOPS moderadas.
ZFS complica el tamaño de IO porque el tamaño de IO de la aplicación, el recordsize de ZFS y el tamaño físico de sector no siempre coinciden.

Además, una carga de trabajo puede estar limitada por throughput e IOPS en distintos momentos.
Ejemplo: un host de VM puede tener lecturas aleatorias de 8 KiB en estado estable (sensibles a IOPS/latencia), y luego hacer escrituras secuenciales grandes durante ventanas de backup (sensibles a throughput).
Un pool, dos cuellos de botella, una guardia de turno.

Dónde ZFS gasta tiempo: la ruta de rendimiento en términos sencillos

El diagnóstico de rendimiento en ZFS se vuelve más fácil cuando piensas por capas. No “ZFS es lento.” Más bien: “¿esta IO está esperando en qué etapa?”

Ruta de lectura (simplificada)

  1. ¿Hit en ARC? Si sí, estás mayormente en RAM y CPU. La latencia es microsegundos a pocos milisegundos dependiendo de la carga del sistema.
  2. Fallo en ARC → lectura en vdev. Ahora estás a merced de la latencia del disco, la profundidad de cola y la planificación.
  3. Sumas de verificación y descompresión. Coste de CPU, a veces no trivial con NVMe rápido o compresión intensa.
  4. Prefetch puede ayudar lecturas secuenciales, puede perjudicar las aleatorias si contamina el ARC.

Ruta de escritura (simplificada, y donde despiden gente)

  1. Escrituras asíncronas aterrizan en memoria y se confirman en disco en transaction groups (TXGs). Esto es generalmente rápido… hasta que llenas la memoria o el pool no puede vaciar lo suficiente rápido.
  2. Escrituras síncronas deben confirmarse de forma segura antes de la acusación de recibo. Sin un log dedicado (SLOG), eso significa golpear los vdevs principales con garantías de baja latencia.
  3. Copy-on-write cambia la historia de “sobrescribir”. ZFS asigna bloques nuevos y actualiza metadata. La fragmentación puede volverse un impuesto para cargas aleatorias con el tiempo.

Los vdevs son la unidad de paralelismo

ZFS hace striping entre vdevs, no entre discos individuales como algunos asumen vagamente.
Un solo vdev RAIDZ tiene un perfil de IOPS limitado (especialmente para escrituras aleatorias) comparado con múltiples vdevs mirror.
Si quieres más IOPS aleatorios, normalmente agregas más vdevs, no “discos más grandes.”

El encolamiento es donde muere tu latencia

La mayoría de los incidentes de rendimiento son incidentes de encolamiento.
Los discos no están “lentos” tanto como “ocupados”, y ocupado significa que las solicitudes esperan.
Esa espera aparece como latencia. El pool puede seguir mostrando throughput respetable, porque los bytes siguen moviéndose, pero cada IO individual está esperando en la fila.

Huellas de carga: cómo se ven “IOPS-bound” y “throughput-bound”

IOPS-bound (IO aleatorio pequeño)

Huellas:

  • Alta latencia aunque MB/s sea modesto.
  • Tamaño de IO pequeño (típico 4–16 KiB).
  • Profundidad de cola aumenta bajo carga; los discos muestran síntomas tipo await o svctm según la herramienta.
  • CPU a menudo no está saturada; estás esperando al almacenamiento.
  • Datasets ZFS con recordsize pequeño o zvols con volblocksize pequeño tienden a amplificarlo.

Culpables típicos:
hosts de VM, bases de datos OLTP, árboles de archivos con mucho metadata, contenedores con sistemas de archivos en capas que golpean archivos pequeños.

Throughput-bound (IO secuencial grande)

Huellas:

  • Alto MB/s y latencia estable hasta que saturas.
  • Tamaño de IO grande (128 KiB hasta varios MiB).
  • CPU puede convertirse en cuello de botella si compresión/sumadeverificación consumen mucho.
  • Red frecuentemente se convierte en el techo (10/25/40/100GbE), especialmente con clientes NFS/SMB.

Culpables típicos:
flujos de backup, pipelines de medios, escaneos analíticos grandes, replicación y operaciones de resilver.

La trampa de la carga mixta

Tu pool puede verse increíble de noche (gráficas de throughput de backups) y terrible al mediodía (latencia interactiva).
Si dimensionas solo por throughput, entregarás un sistema que se derrite bajo IO aleatorio.
Si dimensionas solo por IOPS, puedes comprar SSDs caros cuando en realidad necesitabas más red o mejor layout secuencial.

Hechos interesantes y contexto histórico (que realmente ayudan)

  • ZFS fue diseñado en Sun con integridad de datos primero: sumas de verificación end-to-end y copy-on-write fueron características centrales, no añadidos.
  • RAIDZ existe para evitar el write hole de RAID-5, intercambiando algo de rendimiento en escrituras pequeñas por garantías más fuertes.
  • El ARC no es “solo caché”; es una caché autoajustable con conciencia de metadata, y puede cambiar dramáticamente el comportamiento de la carga.
  • Las unidades con sector 4K cambiaron todo: ashift mal alineado causó colapsos de rendimiento en el mundo real y amplificación de escritura impredecible.
  • Prefetch se construyó para lecturas en streaming; en cargas con IO aleatorio intenso puede desperdiciar ARC y presupuesto de IO si no entiendes cuándo se activa.
  • Los dispositivos SLOG se hicieron populares porque las escrituras sync se volvieron reales cuando la virtualización y las bases de datos empezaron a usar fsync por defecto.
  • La compresión pasó de “impuesto de CPU” a “característica de rendimiento” a medida que las CPU se volvieron más rápidas y el almacenamiento relativamente más lento; menos bytes puede significar menos IOs.
  • Los vdevs especiales son una respuesta moderna a un problema antiguo: metadata y bloques pequeños son sensibles a la latencia mientras los datos a granel pueden ser más lentos.
  • El marketing de IOPS vino de la era HDD, donde el acceso aleatorio era brutalmente lento y “seeks por segundo” básicamente definía clases de rendimiento.

Guía rápida de diagnóstico

Este es el orden que uso cuando me llaman. Está sesgado a encontrar el cuello de botella correcto antes de tocar perillas.

Primero: demuestra si es latencia, IOPS, throughput, CPU o red

  1. Comprueba latencia y encolamiento en el servidor (no en el cliente): iostat, zpool iostat, y p95/p99 de la aplicación.
  2. Comprueba tamaño y patrón de IO: aleatorio vs secuencial, sync vs async, lectura vs escritura. Si no conoces el patrón, tu benchmark es fan fiction.
  3. Comprueba rate de aciertos en ARC: si ARC te está salvando, los discos pueden ser inocentes.

Segundo: localiza el cuello de botella a un vdev, dataset/zvol o ruta

  1. ¿Qué vdev está caliente? Mirrors vs RAIDZ se comportan muy diferente bajo escrituras aleatorias.
  2. ¿Es sync el problema? Busca latencia de escrituras sync y salud del SLOG.
  3. ¿Es metadata el problema? Cargas con muchos archivos pequeños suelen morir por IOPS de metadata, no por throughput de datos.

Tercero: decide la palanca

  • Si está IOPS-bound: añade vdevs, cambia a mirrors, usa vdev especial para metadata/bloques pequeños, ajusta recordsize/volblocksize, reduce la presión sync o añade un SLOG apropiado.
  • Si está throughput-bound: comprueba la red, revisa CPU por compresión/checksums, amplia stripes (más vdevs) y usa recordsize mayor para datasets de streaming.
  • Si es latencia por encolamiento: reduce concurrencia, aísla vecinos ruidosos, configura saneamente primarycache/secondarycache, y deja de mezclar cargas que se odian entre sí.

Tareas prácticas: comandos, qué significa la salida y qué decisión tomar

Estas son tareas reales de “haz esto ahora”. Cada una tiene un comando, salida de ejemplo, lo que significa y la decisión que impulsa.
Ejecútalas en el host de almacenamiento siempre que sea posible. Las métricas del lado cliente son útiles, pero mienten por omisión.

Task 1: Identificar la topología del pool (porque el layout de vdev es destino)

cr0x@server:~$ sudo zpool status -v tank
  pool: tank
 state: ONLINE
  scan: scrub repaired 0B in 0 days 02:11:09 with 0 errors on Sun Dec 22 03:10:12 2025
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          mirror-0                  ONLINE       0     0     0
            nvme0n1p2               ONLINE       0     0     0
            nvme1n1p2               ONLINE       0     0     0
          raidz1-1                  ONLINE       0     0     0
            sda2                    ONLINE       0     0     0
            sdb2                    ONLINE       0     0     0
            sdc2                    ONLINE       0     0     0

errors: No known data errors

Significado: Este pool mezcla un vdev mirror y un vdev RAIDZ1. Está permitido, pero las características de rendimiento difieren mucho; la asignación hará striping entre vdevs y la parte más lenta/contendida puede dominar la latencia.

Decisión: Si te importa la latencia consistente, evita mezclar tipos de vdev en el mismo pool. Si ya existe, considera separar cargas por pool.

Task 2: Observar IO del pool a alto nivel (IOPS vs MB/s)

cr0x@server:~$ sudo zpool iostat -v tank 1 5
               capacity     operations     bandwidth
pool         alloc   free   read  write   read  write
----------  -----  -----  -----  -----  -----  -----
tank         3.21T  5.89T  3.21K  1.87K   110M  42.3M
  mirror-0    920G  1.81T  2.95K    210  98.4M  3.21M
    nvme0n1p2     -      -  1.48K    105  49.2M  1.60M
    nvme1n1p2     -      -  1.47K    105  49.2M  1.61M
  raidz1-1    2.29T  4.08T    260  1.66K  11.7M  39.1M
    sda2          -      -     90    560  4.10M  13.0M
    sdb2          -      -     86    550  3.96M  13.1M
    sdc2          -      -     84    545  3.64M  13.0M

Significado: El vdev RAIDZ está absorbiento la mayoría de las escrituras (1.66K ops), mientras las lecturas están dominadas por el mirror. Eso probablemente significa que la carga es intensiva en escrituras y aterriza donde la latencia será peor.

Decisión: Si la latencia importa, mueve datasets sensibles a escrituras a un pool compuesto por mirrors o vdevs SSD; o rediseña los vdevs. No “tunes” tu salida de una descoincidencia topológica.

Task 3: Comprobar latencia/encolamiento en el layer de bloques

cr0x@server:~$ iostat -x 1 3
Linux 6.8.0 (server)  12/25/2025  _x86_64_  (32 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.44    0.00    6.21    9.87    0.00   71.48

Device            r/s     w/s   rMB/s   wMB/s  rrqm/s  wrqm/s  %util  await  r_await  w_await
nvme0n1          480.0   42.0   62.0     1.9     0.0     8.0   38.2   1.20    1.10    2.30
nvme1n1          475.0   41.0   61.8     1.9     0.0     7.0   37.9   1.18    1.08    2.25
sda               22.0  180.0    1.2    20.5     0.0    15.0   96.8  24.40    9.20   26.10
sdb               21.0  178.0    1.2    20.2     0.0    14.0   95.9  25.10    9.50   26.70
sdc               20.0  175.0    1.1    19.9     0.0    13.0   95.1  26.00   10.10   27.20

Significado: Los HDD están al ~95% de utilización con ~25 ms de await. NVMe está bien (~1.2 ms). Esto es encolamiento clásico: los IO pequeños a vdevs HDD serán castigados.

Decisión: Si tu carga necesita baja latencia, deja de enviarla a vdevs HDD saturados. Reduce concurrencia, mueve datasets o añade vdevs/SSDs. No te concentres en MB/s.

Task 4: Identificar si hay escrituras sync involucradas

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

Significado: standard significa que la aplicación decide sync vs async (fsync/O_DSYNC). Muchas bases de datos e hipervisores forzarán semánticas sync.

Decisión: Si ves alta latencia de escritura y la app hace muchos fsync, investiga SLOG y la ruta sync. No “arregles” esto poniendo sync=disabled a menos que disfrutes de autopsias por pérdida de datos.

Task 5: Comprobar presencia y salud de SLOG (log separado)

cr0x@server:~$ sudo zpool status tank | sed -n '1,120p'
  pool: tank
 state: ONLINE
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          mirror-0                  ONLINE       0     0     0
            nvme0n1p2               ONLINE       0     0     0
            nvme1n1p2               ONLINE       0     0     0
          raidz1-1                  ONLINE       0     0     0
            sda2                    ONLINE       0     0     0
            sdb2                    ONLINE       0     0     0
            sdc2                    ONLINE       0     0     0
        logs
          mirror-2                  ONLINE       0     0     0
            nvme2n1p1               ONLINE       0     0     0
            nvme3n1p1               ONLINE       0     0     0

errors: No known data errors

Significado: Existe un SLOG en espejo. Bien: la latencia de escrituras sync puede estar limitada por estos dispositivos, no por el RAIDZ de HDD.

Decisión: Valida que los dispositivos SLOG sean seguros contra pérdida de energía y no sean NVMe de consumo que pretenden ser fiables. Si falta SLOG y las sync son intensas, considera añadir uno—pero solo después de confirmar que la carga está limitada por sync.

Task 6: Comprobar recordsize del dataset (throughput vs comportamiento de lecturas aleatorias)

cr0x@server:~$ sudo zfs get -o name,property,value recordsize tank/app
NAME      PROPERTY    VALUE
tank/app  recordsize  128K

Significado: 128K es un buen valor por defecto para archivos generales. Para bases de datos que usan páginas de 8K, puede causar amplificación de lectura (leer más de lo necesario) e impactar la latencia cuando hay fallos de caché.

Decisión: Para datasets de bases de datos, considera recordsize=16K o 8K dependiendo del tamaño de página y patrón de acceso. Para datasets de streaming, mantenlo mayor (128K–1M).

Task 7: Comprobar volblocksize de zvol (las VM viven y mueren aquí)

cr0x@server:~$ sudo zfs get -o name,property,value volblocksize tank/vmstore/vm-001
NAME                  PROPERTY      VALUE
tank/vmstore/vm-001   volblocksize  8K

Significado: 8K coincide con muchos patrones de IO aleatorio de VM, pero puede reducir throughput secuencial e incrementar sobrecarga de metadata. Muy pequeño también significa más operaciones IO por los mismos bytes.

Decisión: Elige volblocksize por carga antes de escribir datos (es fijo tras la creación en muchas implementaciones). Para cargas mixtas de VM, 8K–16K es común; para secuencial a granel, uno mayor puede ayudar.

Task 8: Comprobar estadísticas de ARC (¿estás limitado por disco o por caché?)

cr0x@server:~$ arcstat 1 3
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
12:10:01  3200   420     13   180    5   210    7    30    1   96G   110G
12:10:02  3400   600     17   210    6   360   11    30    1   96G   110G
12:10:03  3100   380     12   160    5   190    6    30    1   96G   110G

Significado: Tasa de fallos ~12–17%. No es terrible, pero bajo un incidente de latencia incluso “solo” 15% de misses puede ser catastrófico si los misses van a vdevs lentos.

Decisión: Si los misses se correlacionan con picos de latencia, estás limitado por latencia de disco. Considera más RAM, vdev especial o mover el working set caliente a vdevs más rápidos.

Task 9: Comprobar ratio de compresión (puede ser throughput gratis)

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

Significado: 1.62x significa que escribes menos bytes de los que la app cree. Eso a menudo mejora throughput e IOPS (menos IO físico), a costa de algo de CPU.

Decisión: Si la CPU no está saturada y los datos se comprimen, deja la compresión activada. Si la CPU está al máximo y el almacenamiento está inactivo, evalúa cambiar el nivel de compresión o mover la compresión fuera de caminos calientes.

Task 10: Confirmar ashift (alineación) para evitar amplificación de escritura silenciosa

cr0x@server:~$ sudo zdb -C tank | sed -n '/ashift/,+2p'
        ashift: 12
        asize: 7.99T
        is_log: 0

Significado: ashift=12 significa sectores de 4K. Buena línea base. Si tuvieras ashift=9 en discos 4K, correrías el riesgo de penalidades read-modify-write y comportamiento de escrituras pequeñas horrible.

Decisión: Si ashift es incorrecto, no lo “tunes” para arreglarlo. Reconstruyes vdevs correctamente. Sí, es molesto. No, no es opcional.

Task 11: Comprobar aceleración de metadata/bloques pequeños (vdev special)

cr0x@server:~$ sudo zpool status tank | sed -n '/special/,+15p'
        special
          mirror-3                  ONLINE       0     0     0
            nvme4n1p1               ONLINE       0     0     0
            nvme5n1p1               ONLINE       0     0     0

Significado: Existe un vdev special. Si está configurado con un umbral de bloques pequeños, metadata y bloques pequeños pueden vivir en media rápida, mejorando IOPS/latencia para cargas de archivos pequeños.

Decisión: Si tu carga es intensiva en metadata o IO aleatorio de 4–16K, un vdev special puede cambiar las reglas del juego. Pero trátalo como un vdev de primera clase: redundancia y monitorización son innegociables.

Task 12: Medir comportamiento de IO a nivel de dataset (¿quién es ruidoso?)

cr0x@server:~$ sudo zfs iostat -r -v tank 1 3
                              capacity     operations     bandwidth
pool                       alloc   free   read  write   read  write
-------------------------  -----  -----  -----  -----  -----  -----
tank                        3.21T  5.89T  3.20K  1.86K   110M  42.0M
  tank/app                  820G  1.20T  1.90K   920    52.0M  18.0M
  tank/vmstore               1.1T  2.20T  1.10K   910    58.0M  22.0M
  tank/backups               1.2T  1.50T   200     30   0.8M  2.0M
-------------------------  -----  -----  -----  -----  -----  -----

Significado: El dataset app y el VM store dominan IO. Los backups están tranquilos ahora.

Decisión: Si necesitas aislamiento, coloca VMs y bases de datos en pools separados o al menos en clases de vdev separadas. Intentar “compartir” latencia de almacenamiento en un solo pool es cómo te sacas canas.

Task 13: Determinar si estás limitado por CPU por checksumming/compresión

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

12:12:01 PM  CPU   %usr %nice  %sys %iowait  %irq  %soft  %steal  %idle
12:12:02 PM  all  62.0   0.0  25.0     1.0   0.0    1.0     0.0   11.0
12:12:02 PM   0  78.0   0.0  19.0     0.0   0.0    0.0     0.0    3.0
12:12:02 PM   1  80.0   0.0  17.0     0.0   0.0    0.0     0.0    3.0

Significado: CPU está muy utilizada mientras iowait es bajo. Eso sugiere que el medio de almacenamiento puede estar bien y estás quemando CPU en compresión, checksums, cifrado o simplemente la propia carga.

Decisión: Antes de comprar discos, perfila CPU y revisa costes de características de ZFS. Si estás limitado por CPU, discos más rápidos no ayudarán; más CPU o elecciones de compresión/cifrado distintas podrían.

Task 14: Ejecutar un fio veraz para latencia de lectura aleatoria

cr0x@server:~$ fio --name=randread4k --filename=/tank/app/fio-testfile --size=8G --rw=randread --bs=4k --iodepth=32 --numjobs=4 --direct=1 --runtime=30 --time_based --group_reporting
randread4k: (groupid=0, jobs=4): err= 0: pid=27144: Thu Dec 25 12:13:40 2025
  read: IOPS=48210, BW=188MiB/s (197MB/s)(5640MiB/30001msec)
    slat (nsec): min=1100, max=220000, avg=5200.4, stdev=3400.1
    clat (usec): min=70, max=9200, avg=640.8, stdev=310.5
     lat (usec): min=80, max=9300, avg=650.9, stdev=312.0
    clat percentiles (usec):
     |  1.00th=[  160],  5.00th=[  240], 10.00th=[  290], 50.00th=[  610]
     | 90.00th=[ 1050], 95.00th=[ 1400], 99.00th=[ 2100], 99.90th=[ 4200]

Significado: Lecturas aleatorias 4K: ~48K IOPS, ~188 MiB/s, p99 ~2.1 ms. Esto es una historia de IOPS/latencia, no de throughput.

Decisión: Compara esto con los requisitos de la aplicación. Si la app necesita p99 sub-milisegundo y no lo alcanzas, necesitas vdevs más rápidos, más paralelismo de vdev, mejor caché o menos presión sync—no “más GB/s.”

Task 15: Ejecutar un fio de throughput secuencial (separado del aleatorio)

cr0x@server:~$ fio --name=seqread1m --filename=/tank/backups/fio-testfile --size=32G --rw=read --bs=1m --iodepth=16 --numjobs=2 --direct=1 --runtime=30 --time_based --group_reporting
seqread1m: (groupid=0, jobs=2): err= 0: pid=27201: Thu Dec 25 12:15:12 2025
  read: IOPS=3020, BW=3020MiB/s (3168MB/s)(90615MiB/30005msec)
    clat (usec): min=230, max=12000, avg=820.3, stdev=310.4

Significado: Puedes hacer ~3 GiB/s de lecturas secuenciales con IOs de 1 MiB. Genial para backups/streams.

Decisión: Usa esto para dimensionar ventanas de replicación y rendimiento de backups. No lo uses para reclamar que la base de datos será rápida.

Task 16: Comprobar presión de TXG (¿se están acumulando escrituras?)

cr0x@server:~$ cat /proc/spl/kstat/zfs/txg | sed -n '1,80p'
13 1 0x01 7 336 4257451234 123456789
name                            type data
birth                           4    1756114458
state                           4    1
txg                             4    231948
g__active                       4    231949
g__opened                       4    231948
g__quiescing                    4    231947
g__syncing                      4    231946
ndirty                          4    1948723200
dirty_max                       4    4294967296
delay                           4    0

Significado: ndirty es grande pero por debajo de dirty_max, y delay es 0. Si delay sube o ndirty llega al máximo, las aplicaciones pueden recibir throttling; suelen seguir picos de latencia.

Decisión: Si los TXG luchan, estás limitado por flush de escrituras. Observa la latencia de escritura de vdev, la carga sync, y si un RAIDZ lento está respaldando la tubería.

Tres microhistorias del mundo corporativo (anonimizadas, plausibles y técnicamente molestas)

1) El incidente causado por una suposición equivocada: “Pero el pool hace 5 GB/s”

Una compañía SaaS mediana migró un servicio de pagos desde un SAN viejo a un flamante appliance ZFS.
La lista de verificación de la migración tenía los elementos habituales: horario de scrub, snapshots, replicación, alertas. La sección de rendimiento tenía una línea: “validad throughput con dd.”
Ejecutaron una gran lectura secuencial, obtuvieron un número heroico y lo pusieron en producción.

Dos semanas después, desplegaron una función que aumentó el volumen de transacciones y añadió un par de índices secundarios.
La base de datos no se cayó. Simplemente se volvió lenta. La latencia subió gradualmente hasta que la API empezó a hacer timeouts.
El canal de incidentes se llenó de gráficas que mostraban que el pool solo hacía unos pocos cientos de MB/s. “Tenemos margen,” dijo alguien, y todos asintieron porque MB/s se veía bajo.

El verdadero problema era la latencia en lecturas aleatorias. El working set ya no cabía en ARC, así que los fallos de caché iban a un vdev RAIDZ de HDDs.
Cada consulta necesitaba docenas de lecturas de 8–16 KiB, y esas lecturas se encolaban detrás de otros IO aleatorios.
El throughput se mantuvo modesto porque el tamaño de IO era pequeño; las IOPS eran el cuello de botella y el encolamiento multiplicó el dolor.

Lo arreglaron moviendo el dataset de la base de datos a un pool SSD solo con mirrors y ajustando recordsize apropiadamente.
La gráfica de throughput apenas cambió. La latencia p95 cayó dramáticamente. El equipo aprendió a la fuerza que los usuarios no te pagan en gigabytes por segundo.

2) La optimización que salió mal: “Forcemos registros más grandes para rendimiento”

Otra empresa operaba una flota de hosts de VM en ZFS con zvols.
Alguien notó que el throughput secuencial no era estelar durante ventanas de backup y decidió “optimizar”: estandarizaron todo a tamaños de bloque mayores.
Los datasets obtuvieron recordsize mayor; algunos zvols se recrearon con volblocksize mayor; la solicitud de cambio afirmaba con orgullo “menos IOs.”

Los backups mejoraron un poco. Luego la cola de tickets empezó a llenarse de quejas: las VMs se sentían lentas, especialmente durante parches y picos de inicio de sesión.
Las gráficas mostraban mayor latencia pero también mayor throughput en horas punta, lo que parecía un éxito para quien solo miraba ancho de banda.

El problema fue la amplificación de lectura y la ineficiencia de caché. Muchas VMs hacían lecturas/escrituras aleatorias de 4–8 KiB.
Bloques más grandes significaron que cada acceso pequeño traía más datos de los necesarios, desperdiciando ARC y generando más IO físico en fallos de caché.
Peor aún, las escrituras aleatorias causaron más trabajo por operación y la capacidad efectiva de IOPS del pool bajó.

Revirtieron para zvols de VM: volblocksize más pequeño, y separaron los streams de backup a un dataset/pool distinto.
La lección fue simple: no optimizas almacenamiento haciéndolo todo “grande”. Lo optimizas casando el comportamiento de bloque con la carga.

3) La práctica aburrida pero correcta que salvó el día: “Manteníamos sync honesto”

Una empresa financiera operaba almacenamiento NFS para varios sistemas críticos, incluyendo un cluster de bases de datos y una cola de mensajería.
El equipo de almacenamiento tenía una política que molestaba a los desarrolladores: las semánticas sync permanecían habilitadas, y “arreglos de rendimiento” que implicaban deshabilitar seguridad requerían aprobación de riesgo.
No era popular, pero era consistente.

Durante una renovación de hardware, un vendedor sugirió un atajo: poner sync=disabled en los datasets calientes y “hacerlo gritar”.
El equipo de almacenamiento se negó y en su lugar añadió un SLOG en espejo correcto construido sobre dispositivos con protección contra pérdida de energía, luego midieron latencia de fsync bajo carga.
El resultado no fue mágico, pero fue estable. Más importante: fue predecible.

Meses después, tuvieron un evento de energía que dejó fuera un PDU de rack de forma que acortó el tiempo de los UPS más de lo esperado.
Algunos sistemas cayeron de golpe. El almacenamiento volvió limpio.
El postmortem fue aburrido—sin pérdida de datos, sin corrupción, sin comportamiento extraño en la reproducción—y aburrido era la meta.

Si quieres una cita de fiabilidad para colgar en tu escritorio, aquí hay una que confío porque es operativa:
“La esperanza no es una estrategia.” — General Gordon R. Sullivan

Perillas de ZFS que cambian IOPS vs throughput (y las que no)

Topología: mirrors vs RAIDZ

Los mirrors generalmente ganan en IOPS de lectura aleatoria y a menudo en latencia de escritura aleatoria. RAIDZ tiende a ganar en eficiencia de capacidad y a menudo en throughput secuencial por dólar, pero paga un impuesto en escrituras aleatorias pequeñas (cálculo de paridad + patrones de IO).
Si tu carga es sensible a IOPS/latencia, los mirrors son la respuesta por defecto salvo que tengas una razón sólida para lo contrario.

recordsize (datasets) y volblocksize (zvols)

Estas configuraciones influyen en la amplificación de IO y en la eficiencia de caché.
Bloques más grandes ayudan el throughput secuencial y reducen la sobrecarga de metadata. Bloques más pequeños pueden reducir la amplificación de lectura para lecturas aleatorias pequeñas.
Establécelos según el tamaño de IO y el patrón de acceso de la aplicación. Si no conoces el patrón de la app, descúbrelo. Adivinar es cómo construyes decepciones caras.

sync, SLOG y el verdadero significado de “escrituras rápidas”

Las escrituras sync son sobre garantías de durabilidad. Si la app lo exige, ZFS debe confirmar de forma segura antes de reconocer la operación.
Un buen SLOG puede reducir la latencia de escrituras sync al tomar ese golpe de durabilidad en un dispositivo rápido y de baja latencia, para luego vaciarlo al almacenamiento principal.

Un SLOG malo (SSD sin PLP, sobrecargado o compartido con otras cargas) es peor que ninguno: se vuelve un cuello de botella y añade riesgo de fallo.

vdev special para metadata y bloques pequeños

Si tu dolor es IO de metadata (muchos archivos pequeños, recorridos de directorio, buzones de correo) o lecturas aleatorias de bloques pequeños, los vdevs special pueden mover las partes más calientes y sensibles a latencia a SSD.
Esto suele ser una solución más limpia que tirar más RAM al ARC cuando el working set es demasiado grande.

Compresión

La compresión cambia la matemática. Si comprimes 2:1, reduces a la mitad bytes físicos y a menudo el tiempo de IO físico.
Pero gastas ciclos de CPU. En pools HDD suele ser una victoria. En pools NVMe a throughput extremo, la CPU puede convertirse en el techo.

Qué no obsesionar

Pequeños “tunables” no arreglarán una descoincidencia workload/topología.
Si tienes RAIDZ HDDs sirviendo escrituras aleatorias de 8K sync, ninguna sysctl te rescatará. Los discos aún tendrán que hacer el trabajo, una dolorosa operación a la vez.

Broma #2: Ajustar sin medición es como depurar con los ojos cerrados—técnicamente posible, pero mayormente una forma de baile interpretativo.

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

1) “Throughput es bajo, el almacenamiento debe estar bien” (mientras la latencia es horrible)

Síntomas: Timeouts de la app, alta latencia p95/p99, pero las gráficas de MB/s se ven modestas.

Causa raíz: Carga bounded por IOPS con tamaño de IO pequeño; encolamiento en vdevs; misses de caché alcanzando medios lentos.

Solución: Mide tamaño de IO y latencia; añade paralelismo de vdev (más mirrors), mueve datasets calientes a SSD, añade vdev special o incrementa ARC si realmente cabe.

2) “Compramos discos más rápidos, sigue lento”

Síntomas: NVMe por todas partes, pero ganancias pequeñas; CPU caliente; latencia no mejora como se esperaba.

Causa raíz: Limitación por CPU (compresión/cifrado/checksums), o el cuello de botella es la red/NFS/SMB, o la app está serializada (IO single-threaded).

Solución: Revisa utilización de CPU y red; perfila concurrencia de la app; valida que los clientes puedan emitir IO paralelo; ajusta en la capa correcta.

3) “Las escrituras aleatorias colapsaron después de llenar el pool”

Síntomas: Pool nuevo era rápido; tras meses, picos de latencia en IO aleatorio; liberar espacio ayuda un poco.

Causa raíz: Fragmentación + presión de asignación en copy-on-write, además de menor espacio libre que conduce a asignación menos eficiente.

Solución: Mantén margen de espacio libre saludable, especialmente en RAIDZ HDD; usa vdev special para metadata/bloques pequeños; considera reescribir o migrar datasets; evita patrones patológicos de sobrescritura en RAIDZ para cargas calientes aleatorias.

4) “Las escrituras sync nos mataban, así que deshabilitamos sync”

Síntomas: El rendimiento “mejora” inmediatamente; más tarde ves corrupción tras un crash, o la replicación envía bloques rotos.

Causa raíz: Intercambiar durabilidad por velocidad; el sistema estaba limitado por sync y necesitaba un SLOG apropiado o cambio en la carga.

Solución: Restaura sync=standard; añade un SLOG en espejo con protección contra pérdida de energía; o cambia el comportamiento de la app conscientemente (batch fsync, group commit), no engañándola.

5) “Cambiamos recordsize y empeoró el rendimiento”

Síntomas: Trabajos secuenciales mejoran, pero cargas interactivas degradan; tasa de aciertos de caché baja; más operaciones IO por el mismo trabajo.

Causa raíz: Tamaños de bloque desalineados vs carga; amplificación de lectura; contaminación de ARC.

Solución: Alinea recordsize/volblocksize al patrón de acceso: pequeño para lecturas aleatorias de DB, grande para streaming. Separa cargas en datasets distintos y aplica propiedades por dataset.

6) “Un pool para todo”

Síntomas: Backups vuelven lentas las bases de datos; scrubs hacen que las VMs tengan jitter; rendimiento impredecible.

Causa raíz: Patrones de IO en competición; sin aislamiento; colas de vdev compartidas.

Solución: Separa pools o al menos clases de vdev; programa scrubs/resilvers; limita trabajos a granel; considera targets de backup dedicados.

Listas de verificación / plan paso a paso

Paso a paso: decide si estás IOPS-bound o throughput-bound

  1. Recopila latencia (p95/p99) desde la app y desde el host de almacenamiento (iostat -x).
  2. Recopila distribución de tamaño de IO usando pruebas fio que coincidan con la app (4K random, 8K sync writes, 1M lecturas secuenciales, etc.).
  3. Calcula el throughput implícito: IOPS × tamaño de IO. Si tu MB/s observado coincide con la matemática, las métricas son consistentes y puedes razonar con claridad.
  4. Comprueba tasa de fallos en ARC. Alta tasa de misses con alto await en disco significa que estás limitado por disco; alta tasa de misses con bajo await en disco sugiere otra cosa (CPU/red/app).
  5. Inspecciona utilización de vdev via zpool iostat -v. Identifica el vdev más caliente y por qué está caliente.

Checklist: diseñando para IOPS aleatorios (bases de datos, VMs)

  • Prefiere múltiples vdevs mirror sobre un pequeño número de vdevs RAIDZ anchos.
  • Configura recordsize/volblocksize para coincidir con tamaños de IO esperados.
  • Considera vdev special para metadata/bloques pequeños.
  • Mantén margen de espacio libre saludable (no operes pools “casi llenos” y luego te sorprendas).
  • Asegura que la ruta sync sea sensata: o un SLOG apropiado o acepta la latencia y diseña en torno a ella.

Checklist: diseñando para throughput secuencial (backups, ETL)

  • Usa recordsize más grande para archivos grandes.
  • Mide techos de red; el almacenamiento puede no ser tu cuello de botella.
  • Confirma margen de CPU si usas compresión/cifrado.
  • Separa trabajos secuenciales de alto volumen de datasets interactivos sensibles si es posible.

Checklist operativo: antes de tocar perillas

  • Captura zpool status, zpool iostat -v, iostat -x durante el incidente.
  • Captura estadísticas de ARC e indicadores de presión de memoria.
  • Escribe el patrón de la carga (sync? aleatorio? tamaño de IO?). Si no puedes, detente e instrumenta.
  • Haz un cambio a la vez y vuelve a medir. El almacenamiento no es una disciplina basada en vibras.

Preguntas frecuentes

1) ¿Qué métrica debo vigilar primero en ZFS: IOPS o throughput?

Vigila la latencia primero (p95/p99), luego relaciónala con IOPS y tamaño de IO. El throughput por sí solo es un número agradable y a menudo el equivocado.

2) ¿Por qué mi pool muestra alto throughput pero mi base de datos está lenta?

Tu pool puede ser bueno en lecturas/escrituras secuenciales (alto MB/s) mientras la base de datos necesita IO aleatorio pequeño y baja latencia.
Si las lecturas aleatorias fallan en ARC y van a RAIDZ HDD, la latencia domina y el throughput no refleja el dolor del usuario.

3) ¿Los mirrors siempre son más rápidos que RAIDZ?

Para IOPS aleatorios y latencia, los mirrors suelen ganar.
Para eficiencia de capacidad y a menudo throughput secuencial por dólar, RAIDZ puede ser atractivo.
Elige según la carga. “Siempre” es para marketing, no para ingeniería.

4) ¿Agregar más discos a un vdev RAIDZ aumenta IOPS?

Puede aumentar throughput secuencial, pero las IOPS aleatorias no escalan linealmente como la gente espera.
Si necesitas IOPS aleatorias, típicamente añades más vdevs (más colas independientes), no solo un RAIDZ más ancho.

5) ¿Cuándo ayuda un SLOG?

Cuando tienes una carga con muchas escrituras sync (DBs fsync-heavy, NFS con sync, ciertos patrones de VM) y tus vdevs principales tienen mayor latencia.
No ayuda escrituras async, y no arregla latencia de lectura aleatoria.

6) ¿Es sync=disabled aceptable alguna vez?

Es aceptable cuando deliberadamente eliges mentir sobre durabilidad y puedes tolerar pérdida de datos en fallo de energía o crash.
La mayoría de los sistemas de producción no pueden, y los que dicen que sí a menudo cambian de opinión tras el primer outage.

7) ¿Debería cambiar recordsize para mi base de datos?

A menudo sí, pero solo con entendimiento. Si el tamaño de página de tu DB es 8K y el acceso es aleatorio, recordsize más pequeño puede reducir amplificación de lectura en misses de caché.
Haz benchmarks con cargas representativas y vigila latencia p95/p99, no solo MB/s.

8) ¿Cómo sé si estoy limitado por ARC o por disco?

Si la tasa de aciertos en ARC es alta y los discos muestran bajo await/util, probablemente estás limitado por caché/CPU/app.
Si los misses de ARC se correlacionan con alto await/util en disco y latencia creciente, estás limitado por disco y necesitas medios más rápidos o más paralelismo de vdev (o un working set más pequeño).

9) ¿Puede la compresión mejorar IOPS?

Sí. Si los datos se comprimen, ZFS escribe menos bytes y puede satisfacer lecturas más rápido (menos IO físico), lo que puede mejorar throughput y IOPS efectivos.
Si la CPU se convierte en cuello de botella, la ganancia desaparece.

10) ¿Por qué los benchmarks difieren con producción?

Porque los benchmarks a menudo son secuenciales, amigables con la caché y con colas de profundidad irrealistas, mientras que producción es mixta, en ráfagas y sensible a la latencia.
Si tu benchmark no coincide con tamaño de IO, comportamiento sync, concurrencia y working set, está midiendo otro universo.

Siguientes pasos que puedes hacer esta semana

Si quieres dejar de discutir sobre “almacenamiento rápido” y empezar a entregar rendimiento predecible, haz esto en orden:

  1. Elige dos perfiles fio representativos: uno aleatorio de bloques pequeños (4K o 8K) con concurrencia realista, y uno secuencial de bloques grandes (1M). Ejecútalos en los mismos datasets que usan tus apps.
  2. Instrumenta la latencia: captura p95/p99 en la aplicación y en el host de almacenamiento durante la carga pico.
  3. Audita topología vs carga: si ejecutas cargas sensibles a latencia en vdevs RAIDZ HDD, acepta la física o rediseña con mirrors/SSD/special vdev.
  4. Ajusta tamaños de bloque: alinea recordsize/volblocksize a cómo la app realmente hace IO, no a cómo desearías que lo haga.
  5. Valida comportamiento sync: no deshabilites seguridad en producción; añade un SLOG apropiado si las escrituras sync son el factor limitante.
  6. Separa vecinos ruidosos: backups, scrubs y replicación son necesarios. También arrasan la latencia interactiva si dejas que compartan colas sin control.

Luego vuelve a medir. Si no puedes mostrar “antes vs después” en percentiles de latencia y forma de IO, no hiciste ingeniería—hiciste pensamiento deseoso con privilegios de root.

← Anterior
Ubuntu 24.04 “grub rescue>”: Recupera un sistema arrancable con el menor daño
Siguiente →
IA en todo: cuando las etiquetas fueron más ridículas que las funciones

Deja un comentario