Benchmarking de ZFS: Reglas que evitan resultados falsos

¿Te fue útil?

Alguien te muestra una captura de pantalla: “¡Nuestra nueva máquina ZFS hace 6 GB/s!” Haces una sola pregunta—“¿Desde dónde?”—y la sala se queda en silencio. Porque el dispositivo de almacenamiento más rápido del centro de datos es el que no querías probar: la RAM. El segundo más rápido es el que olvidaste que habías activado: la compresión.

Los benchmarks son la forma en que los proyectos de almacenamiento consiguen financiación, aprobación y luego culpas. En producción, “casi correcto” equivale a incorrecto, solo con gráficas más bonitas. Este es un conjunto de reglas y métodos de campo para medir ZFS sin engañarte a ti mismo, a tu jefe o al siguiente de guardia.

Qué estás realmente midiendo (y qué no)

El benchmarking de ZFS falla por una razón central: la gente evalúa “almacenamiento” como si fuera una sola cosa. No lo es. Estás midiendo una tubería: aplicación → libc → kernel VFS → ZFS DMU → ZIO scheduler → vdev queues → discos/flash → controlador → firmware. Añade ARC, L2ARC, SLOG, compresión, checksums y metadatos, y estás midiendo un sistema con más piezas móviles que una reorganización corporativa.

Hay tres números que importan:

  • Throughput (MB/s o GB/s): velocidad de lectura/escritura a granel. Ideal para backups, medios, escaneos secuenciales, y para engañar en presentaciones.
  • IOPS: operaciones por segundo. Importante para bloques pequeños, acceso aleatorio, churn de metadatos y bases de datos reales.
  • Latencia (especialmente p95/p99): el único número que sienten tus usuarios. ZFS puede entregar alto throughput mientras silenciosamente arruina la latencia en cola.

Un benchmark que produce un solo número y no muestra la distribución de latencias es un chequeo de sensación, no ingeniería.

El benchmarking es inferencia, no medida directa

El almacenamiento es difícil de “medir” directamente porque el sistema se adapta. ARC se calienta. Los transaction groups (TXGs) agrupan trabajo. ZFS prefetch y readahead cambian patrones. La compresión cambia bytes físicos. Si no controlas esas variables, no estás probando el pool: estás probando lo que ZFS decidió hacer mientras mirabas.

Una regla seca: si no puedes explicar por qué un resultado mejoró, no mejoró. Tuviste suerte.

Algunos hechos e historia que cambian cómo haces benchmarks

Un poco de contexto te ayuda a predecir cómo se comportará ZFS en las pruebas. Aquí hay hechos concretos que importan en laboratorio y en producción:

  1. ZFS fue diseñado con integridad de datos de extremo a extremo como objetivo principal, no como un añadido. Checksums, copy-on-write y autocuración influyen en amplificación de escritura y latencia.
  2. El trabajo temprano de ZFS en Sun buscaba que discos commodity se comportaran “suficientemente enterprise”. La idea era sobrevivir hardware poco fiable con software inteligente. Los benchmarks que ignoran las opciones de integridad pierden el sentido.
  3. El ARC no es solo una caché, es un motor de política de memoria. Compite con tu aplicación por RAM y su comportamiento cambia según la carga. Benchmarkear sin controlar ARC es benchmarkear la gestión de memoria.
  4. Copy-on-write significa que “sobrescribir” es en realidad allocate-new + update-metadata. Las sobreescrituras aleatorias se comportan como escrituras aleatorias más churn de metadatos. Los sistemas de archivos que sobrescriben en sitio pueden parecer “más rápidos” en algunos patrones, hasta que corrompen datos.
  5. El batching de TXG es por qué las pruebas cortas mienten. ZFS agrupa escrituras en TXGs y confirma periódicamente. Si haces un benchmark de 10 segundos, puede que nunca veas el comportamiento en estado estable.
  6. SLOG existe porque las escrituras síncronas son caras. ZIL siempre está ahí; SLOG es el dispositivo separado que puede acelerar escrituras sync. Si nunca pruebas comportamiento sync, estás midiendo el riesgo equivocado.
  7. Recordsize es un contrato de rendimiento. ZFS puede almacenar registros grandes eficientemente para cargas secuenciales, pero los registros grandes pueden castigar escrituras aleatorias pequeñas mediante read-modify-write.
  8. Ashift de sector es para siempre. Elige mal y cada escritura puede convertirse en dos. Puedes arreglar muchas cosas en ZFS. No puedes “tunar” un ashift equivocado sin reconstruir.
  9. La compresión a menudo hace a ZFS “más rápido” al hacer menos I/O. Eso no es hacer trampa, a menos que tus datos en producción no compriman. Entonces es el equivalente a correr cuesta abajo en un benchmark.

Una cita para mantenerte honesto:

“La esperanza no es una estrategia.” — General Gordon R. Sullivan

Las reglas: cómo evitar resultados falsos en ZFS

Regla 1: Declara tu pregunta antes de ejecutar un comando

“¿Qué tan rápido es este pool?” no es una pregunta. Haz algo comprobable:

  • ¿Cuál es el IOPS sostenido de lectura aleatoria a 4k con p99 de latencia < 5 ms?
  • ¿Cuál es el throughput de escritura secuencial para bloques de 1M con compresión desactivada?
  • ¿Cuál es la latencia de escrituras sync con y sin SLOG bajo concurrencia 16?
  • ¿Cambiar recordsize de 128K a 16K mejora la latencia de cola de escrituras de la base de datos?

Anota la pregunta. Si no puedes formularla, no podrás interpretar el resultado.

Regla 2: Elige la superficie de prueba correcta: dataset de archivos vs zvol

Probar un dataset de archivos recorre la ruta del sistema de archivos, metadatos y recordsize. Probar un zvol ejerce un dispositivo de bloque, volblocksize y un perfil I/O distinto. Las bases de datos sobre dispositivos crudos (o iSCSI) se comportan distinto que sobre archivos. No hagas benchmark de la interfaz equivocada y luego “optimices” en función de eso.

Regla 3: Controla la caché o mídela explícitamente

ARC puede hacer que las lecturas parezcan sobrenaturales. Está bien si tu working set en producción cabe en RAM. Si no, los benchmarks de lectura impulsados por ARC son fan fiction de rendimiento.

Decide qué estás probando:

  • Rendimiento con caché fría: qué pasa tras un reinicio o tras vaciar la caché, y cuando el working set >> RAM.
  • Rendimiento con caché caliente: la realidad estable de un dataset caliente. Útil, pero no lo confundas con rendimiento de disco.

Segunda regla seca: si no mencionas el estado de la caché en tu informe, los números no son admisibles.

Regla 4: Las escrituras sync son un universo aparte

La mayoría del dolor en producción proviene de las escrituras sync: bases de datos con fsync, NFS con semántica sync, almacenamiento de VM que se preocupa por durabilidad. Los benchmarks async pueden verse gloriosos mientras tu carga real se estrella porque necesita durabilidad.

Haz benchmark de sync explícitamente e incluye percentiles de latencia. Si tienes un SLOG, prueba con él activado y desactivado. Muestra el costo real de durabilidad del sistema.

Regla 5: Usa estado estable; las pruebas cortas mienten

El comportamiento de ZFS cambia con el tiempo: ARC se calienta, metaslabs se asignan, la fragmentación crece y los TXGs se estabilizan. Tu benchmark debe correr lo suficiente para alcanzar estado estable y capturar latencia de cola. Como punto de partida:

  • Pruebas de throughput: 2–5 minutos por configuración, después de un breve calentamiento.
  • Pruebas de latencia aleatoria: al menos 5 minutos, recoge p95/p99/p999.
  • Comparaciones de recordsize / compresión: repite ejecuciones y compara distribuciones, no solo promedios.

Regla 6: Elimina variables “útiles”

Escalonamiento de frecuencia de CPU, scrubs en background, resilvers, pruebas SMART y vecinos ruidosos contaminarán resultados. Los benchmarks no son momento para “compartir la máquina” cortésmente. Sé grosero con el resto del sistema. Puedes disculparte después con uptime.

Regla 7: No midas pools vacíos y los llames producción

La fragmentación y el comportamiento de asignación de metaslab cambian a medida que los pools se llenan. El rendimiento al 5% puede ser excelente; al 80% puede ser otra personalidad. Si te importa producción, prueba a un nivel de llenado realista o simúlalo con preasignación y churn de archivos.

Regla 8: Valida que tu herramienta realmente haga I/O

Las herramientas pueden engañarte. Un fio mal configurado puede benchmarkear page cache. dd puede medir readahead y efectos de caché. Tu monitorización podría estar mirando el dispositivo equivocado.

Confía, pero verifica: durante cualquier prueba, confirma que los discos muestran I/O real y que los contadores de ZFS se mueven.

Regla 9: Mide en múltiples capas

Un número es una trampa. Siempre captura al menos:

  • Nivel aplicación (salida de fio: IOPS, bw, clat percentiles)
  • Nivel ZFS (zpool iostat, arcstat)
  • Nivel dispositivo (iostat -x: util, await, señales tipo svctm)
  • CPU e interrupciones (mpstat, top)

Cuando los resultados parecen “demasiado buenos”, una de esas capas estará sospechosamente quieta.

Regla 10: Cambia una cosa a la vez

El benchmarking no es un festival de tuning. Si cambias recordsize, compresión, sync y atime simultáneamente, estás generando números aleatorios con pasos extra.

Broma #1: Benchmarking es como cocinar—si cambias el horno, la receta y el chef, no te gloríes por el soufflé.

Ajusta la carga: archivos, bloques, sync y latencia

Arquetipos de carga que importan

La mayoría de los casos reales de uso de ZFS caen en unos pocos grupos. Tu benchmark debería imitar uno de ellos:

  • Lecturas secuenciales a granel: backups, medios, escaneos analíticos. Favorece bloques grandes, throughput y efectos de prefetch.
  • Escrituras secuenciales a granel: pipelines de ingest, targets de backup. Observa el comportamiento de TXG y el ancho de banda sostenido tras saturar cachés.
  • Lecturas aleatorias: stores key-value, picos de arranque de VM. ARC puede dominar aquí; importan pruebas con caché fría.
  • Escrituras aleatorias (async): logging, stores temporales. A menudo parece bien hasta que aparece sync.
  • Escrituras aleatorias (sync): bases de datos, NFS sync, datastores de VM con barreras. La latencia y el comportamiento de SLOG son la historia.
  • Pesado en metadatos: servidores git, maildirs, árboles de artefactos CI. Archivos pequeños, operaciones de directorio y un vdev especial pueden importar.

Las propiedades del dataset no son “perillas de tuning”, son contratos de carga

recordsize, compression, atime, primarycache, logbias y sync son decisiones sobre amplificación de escritura, prioridad de caché y semánticas de durabilidad. Hacerles benchmark sin entender la carga es crear un sistema que gana benchmarks y pierde incidentes.

Ejemplo: establecer recordsize=1M en un dataset que soporta una base de datos puede inflar el overhead read-modify-write para actualizaciones de 8K. Podrías ver gran throughput secuencial, y luego ver picos de latencia cuando la base de datos haga actualizaciones aleatorias. El benchmark fue “correcto”. La pregunta fue equivocada.

Tareas prácticas: comandos, salidas, decisiones (12+)

Estas son tareas reales que puedes ejecutar en un host ZFS. Cada una incluye: el comando, qué significa la salida y la decisión que tomas. El objetivo no es coleccionar trivia; es evitar que confíes en un número que miente.

Task 1: Confirmar topología del pool y ashift (el ajuste “para siempre”)

cr0x@server:~$ sudo zpool status -v tank
  pool: tank
 state: ONLINE
  scan: scrub repaired 0B in 0 days 00:12:41 with 0 errors on Tue Dec 24 03:11:03 2025
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          raidz2-0                  ONLINE       0     0     0
            ata-SAMSUNG_SSD_1       ONLINE       0     0     0
            ata-SAMSUNG_SSD_2       ONLINE       0     0     0
            ata-SAMSUNG_SSD_3       ONLINE       0     0     0
            ata-SAMSUNG_SSD_4       ONLINE       0     0     0
            ata-SAMSUNG_SSD_5       ONLINE       0     0     0
            ata-SAMSUNG_SSD_6       ONLINE       0     0     0

errors: No known data errors
cr0x@server:~$ sudo zdb -C tank | grep -E 'ashift|vdev_tree' -n | head
67:        vdev_tree:
92:            ashift: 12

Significado: ashift=12 implica sectores de 4K. ashift=9 implica 512B, y en discos modernos nativos 4K eso puede causar amplificación de escritura y latencia terrible.

Decisión: Si ashift es incorrecto para tu medio, deja de “tunear”. Planifica una reconstrucción o migración. Todo lo demás es maquillaje.

Task 2: Capturar propiedades del dataset que afectan benchmarks

cr0x@server:~$ sudo zfs get -o name,property,value -s local,default recordsize,compression,atime,sync,primarycache,logbias tank/test
NAME       PROPERTY      VALUE
tank/test  recordsize    128K
tank/test  compression   off
tank/test  atime         off
tank/test  sync          standard
tank/test  primarycache  all
tank/test  logbias       latency

Significado: Ahora sabes si el “rendimiento” vino de la compresión, si sync estaba deshabilitado, o si el caching estaba restringido.

Decisión: Congela estas configuraciones para la ejecución del benchmark. Cualquier cambio requiere una nueva ejecución y una razón clara.

Task 3: Verificar que no estés midiendo por accidente la page cache

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            256G         18G        190G        1.2G         48G        235G
Swap:             0B          0B          0B
cr0x@server:~$ sudo arcstat.py 1 3
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
12:01:01   219     3      1     0    0     3  100     0    0   18G   190G
12:01:02   241     2      0     0    0     2  100     0    0   18G   190G
12:01:03   210     4      1     0    0     4  100     0    0   18G   190G

Significado: El tamaño de ARC (arcsz) y hit/miss de caché son visibles. Si tu “benchmark de lectura de disco” muestra misses cercanos a cero, felicidades—hiciste benchmark de RAM.

Decisión: Si necesitas números con caché fría, reinicia entre ejecuciones, usa un working set mayor que ARC, o ajusta primarycache=metadata en el dataset de prueba (con precaución).

Task 4: Confirmar que hay I/O real de dispositivo durante una ejecución

cr0x@server:~$ sudo zpool iostat -v tank 1 3
                                            capacity     operations     bandwidth
pool                                      alloc   free   read  write   read  write
----------------------------------------  -----  -----  -----  -----  -----  -----
tank                                       3.12T  8.77T    120    980  15.0M  980M
  raidz2-0                                 3.12T  8.77T    120    980  15.0M  980M
    ata-SAMSUNG_SSD_1                          -      -     20    165  2.6M  170M
    ata-SAMSUNG_SSD_2                          -      -     19    166  2.4M  169M
    ata-SAMSUNG_SSD_3                          -      -     21    162  2.7M  168M
    ata-SAMSUNG_SSD_4                          -      -     20    161  2.5M  166M
    ata-SAMSUNG_SSD_5                          -      -     20    164  2.4M  169M
    ata-SAMSUNG_SSD_6                          -      -     20    162  2.4M  168M
----------------------------------------  -----  -----  -----  -----  -----  -----

Significado: Puedes ver operaciones y ancho de banda por vdev. Si fio dice “3 GB/s” y zpool iostat muestra 0 MB/s, fio está golpeando la caché o no está haciendo nada.

Decisión: Confía en zpool iostat para validar que tu prueba esté ejerciendo el pool.

Task 5: Comprobar trabajos en background que envenenan resultados

cr0x@server:~$ sudo zpool status tank | sed -n '1,25p'
  pool: tank
 state: ONLINE
  scan: scrub in progress since Wed Dec 25 11:48:03 2025
        1.11T scanned at 4.52G/s, 220G issued at 898M/s, 3.12T total
        0B repaired, 7.03% done, 00:53:12 to go

Significado: Hay un scrub en progreso. Tus resultados ahora son un dúo: tu carga más I/O del scrub.

Decisión: Pausa las pruebas. Espera, programa tests fuera de ventanas de scrub, o deténlo temporalmente si la política lo permite.

Task 6: Establecer una prueba base de escritura secuencial (archivos)

cr0x@server:~$ sudo zfs create -o compression=off -o atime=off tank/bench
cr0x@server:~$ fio --name=seqwrite --directory=/tank/bench --rw=write --bs=1M --size=40G --numjobs=1 --iodepth=16 --direct=1 --time_based=1 --runtime=120 --group_reporting
seqwrite: (groupid=0, jobs=1): err= 0: pid=24819: Wed Dec 25 12:03:44 2025
  write: IOPS=980, BW=981MiB/s (1029MB/s)(115GiB/120001msec)
    clat (usec): min=320, max=18900, avg=2450.11, stdev=702.41
    lat (usec): min=340, max=18940, avg=2475.88, stdev=704.13

Significado: direct=1 evita page cache. Esto es una línea base de throughput, pero aún sujeto a metadatos en ARC y comportamiento de ZFS. La latencia muestra la distribución; los picos máximos importan.

Decisión: Usa esta línea base para comparar cambios (compresión, recordsize, layout de vdevs). No la tomes como “velocidad universal” del pool.

Task 7: IOPS de lectura aleatoria con latencia de cola (frío vs caliente)

cr0x@server:~$ fio --name=randread4k --directory=/tank/bench --rw=randread --bs=4k --size=40G --numjobs=4 --iodepth=32 --direct=1 --time_based=1 --runtime=180 --group_reporting --lat_percentiles=1
randread4k: (groupid=0, jobs=4): err= 0: pid=24910: Wed Dec 25 12:09:12 2025
  read: IOPS=182k, BW=712MiB/s (747MB/s)(125GiB/180002msec)
    clat percentiles (usec):
     |  1.00th=[  118],  5.00th=[  128], 10.00th=[  136], 50.00th=[  176]
     | 90.00th=[  260], 95.00th=[  310], 99.00th=[  560], 99.90th=[ 1400]

Significado: Si esto es “caché fría”, esos números son asombrosos y probablemente ARC está involucrado. Si es caché caliente, puede ser realista para una carga de lectura que cabe en memoria.

Decisión: Repite con un working set mucho mayor que ARC si necesitas comportamiento limitado por disco. Compara percentiles, no solo IOPS.

Task 8: Prueba de escritura sync (la que arruina planes felices)

cr0x@server:~$ sudo zfs set sync=always tank/bench
cr0x@server:~$ fio --name=syncwrite4k --directory=/tank/bench --rw=randwrite --bs=4k --size=10G --numjobs=4 --iodepth=8 --direct=1 --time_based=1 --runtime=180 --group_reporting --lat_percentiles=1
syncwrite4k: (groupid=0, jobs=4): err= 0: pid=25103: Wed Dec 25 12:15:41 2025
  write: IOPS=9800, BW=38.3MiB/s (40.2MB/s)(6.73GiB/180002msec)
    clat percentiles (usec):
     | 50.00th=[  780], 90.00th=[ 1900], 95.00th=[ 2600], 99.00th=[ 6800]
     | 99.90th=[22000]

Significado: Esto refleja el costo de durabilidad. Si esperabas 100k IOPS porque la hoja de especificaciones del SSD lo decía, bienvenido a la diferencia entre velocidad de NAND y semánticas del sistema.

Decisión: Si p99/p999 es demasiado alto, evalúa la calidad del SLOG, el encolamiento y la saturación de CPU. También considera si la carga realmente requiere sync=always o si la durabilidad a nivel de aplicación ya es correcta.

Task 9: Comprobar si hay SLOG y si realmente se usa

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

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          raidz2-0                  ONLINE       0     0     0
            ata-SAMSUNG_SSD_1       ONLINE       0     0     0
            ata-SAMSUNG_SSD_2       ONLINE       0     0     0
            ata-SAMSUNG_SSD_3       ONLINE       0     0     0
            ata-SAMSUNG_SSD_4       ONLINE       0     0     0
            ata-SAMSUNG_SSD_5       ONLINE       0     0     0
            ata-SAMSUNG_SSD_6       ONLINE       0     0     0
        logs
          nvme-INTEL_SLOG0          ONLINE       0     0     0
cr0x@server:~$ sudo zpool iostat -v tank 1 3
                                            capacity     operations     bandwidth
pool                                      alloc   free   read  write   read  write
----------------------------------------  -----  -----  -----  -----  -----  -----
tank                                       3.15T  8.74T     10   1200  1.2M  120M
  raidz2-0                                 3.15T  8.74T     10    900  1.2M   90M
  logs                                         -      -      0    300    0   30M
    nvme-INTEL_SLOG0                           -      -      0    300    0   30M
----------------------------------------  -----  -----  -----  -----  -----  -----

Significado: Durante trabajo intensivo en sync, el vdev de logs debería mostrar escrituras. Si permanece inactivo, tu carga puede no estar emitiendo escrituras sync, o el SLOG puede estar mal aplicado, o no estás probando lo que crees.

Decisión: Si el rendimiento sync es crítico y el SLOG no se usa, corrige la prueba o la configuración antes de culpar al pool.

Task 10: Identificar cuellos de botella CPU y efectos de compresión

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

12:19:01 AM  CPU   %usr %nice %sys %iowait  %irq %soft  %steal  %guest  %gnice  %idle
12:19:02 AM  all   18.2  0.0  22.9    0.4   0.0   2.1    0.0     0.0     0.0   56.4
12:19:03 AM  all   19.0  0.0  24.1    0.5   0.0   2.2    0.0     0.0     0.0   54.2
cr0x@server:~$ sudo zfs set compression=zstd tank/bench
cr0x@server:~$ fio --name=seqwrite-cmpr --directory=/tank/bench --rw=write --bs=1M --size=40G --numjobs=1 --iodepth=16 --direct=1 --time_based=1 --runtime=120 --group_reporting
seqwrite-cmpr: (groupid=0, jobs=1): err= 0: pid=25218: Wed Dec 25 12:20:44 2025
  write: IOPS=1500, BW=1501MiB/s (1574MB/s)(176GiB/120001msec)
    clat (usec): min=350, max=24100, avg=2600.88, stdev=880.03

Significado: El throughput mejoró con compresión. Puede ser legítimo si tus datos de producción comprimen de forma similar. El uso de CPU puede aumentar; la latencia de cola puede cambiar.

Decisión: Valida la compresibilidad con muestras reales o sintéticas representativas. Si los datos de producción ya están comprimidos (medios, blobs encriptados), este benchmark es irrelevante.

Task 11: Comprobar la tasa de compresión real en disco

cr0x@server:~$ sudo zfs get -o name,property,value compressratio,logicalused,used tank/bench
NAME       PROPERTY       VALUE
tank/bench compressratio  1.72x
tank/bench logicalused    180G
tank/bench used           105G

Significado: compressratio muestra lo que pasó, no lo que esperabas. 1.72x significa que escribiste menos datos físicos que lógicos.

Decisión: Si compressratio está cerca de 1.00x en datos representativos, deja de contar con la compresión como plan de rendimiento.

Task 12: Inspeccionar saturación y encolamiento a nivel de dispositivo

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

Device            r/s     w/s   rkB/s   wkB/s  rrqm/s  wrqm/s  %util  await
nvme0n1           0.0   300.0     0.0  30720.0    0.0     0.0   6.5    0.41
sda              25.0   165.0  2560.0 168960.0    0.0     1.0  78.2    2.90
sdb              24.0   166.0  2450.0 169100.0    0.0     1.0  79.0    2.88
sdc              25.0   162.0  2600.0 168100.0    0.0     1.0  77.5    2.95

Significado: %util cercano a 100% con await creciente indica que el dispositivo está saturado. Si los dispositivos no están ocupados pero la latencia es alta, el cuello de botella está en otra parte (CPU, locks, sync, red).

Decisión: Si los discos están saturados, optimiza el layout o añade vdevs. Si los discos están ociosos, deja de comprar discos y comienza a perfilar la pila.

Task 13: Confirmar que la prueba no está limitada por un único archivo o ruta pequeña de metadatos

cr0x@server:~$ fio --name=randread4k-many --directory=/tank/bench --rw=randread --bs=4k --size=40G --numjobs=8 --iodepth=32 --direct=1 --time_based=1 --runtime=180 --group_reporting --filename_format=job.$jobnum.file
randread4k-many: (groupid=0, jobs=8): err= 0: pid=25440: Wed Dec 25 12:28:11 2025
  read: IOPS=240k, BW=938MiB/s (984MB/s)(165GiB/180004msec)
    clat (usec): min=95, max=3200, avg=210.34, stdev=81.22

Significado: Múltiples archivos reducen contención por locks de archivo único y pueden exponer paralelismo. Si el rendimiento sube mucho, tu prueba anterior pudo haber medido un cuello de botella inexistente en producción—o uno que sí existe.

Decisión: Elige la cantidad de archivos para que coincida con la realidad: muchos archivos para imágenes de VM o DB shardadas; menos para logs grandes; zvol para cargas de bloque.

Task 14: Vigilar la latencia de ZFS bajo carga (sanity rápida)

cr0x@server:~$ sudo zpool iostat -r -w tank 1 3
               read                            write
pool   ops  bandwidth  total_wait  disk_wait  ops  bandwidth  total_wait  disk_wait
tank   120     15.0M       1ms         1ms   980     980M       4ms         3ms
tank   110     14.2M       1ms         1ms   995     990M       5ms         4ms
tank   118     15.1M       2ms         1ms   970     970M       4ms         3ms

Significado: total_wait incluye tiempo en colas de ZFS; disk_wait es tiempo en dispositivo. Si total_wait es mucho mayor que disk_wait, el cuello de botella puede estar en scheduling de ZFS, CPU, contención o la ruta sync.

Decisión: Usa la diferencia (total_wait – disk_wait) como pista: ¿estás esperando a discos o a software?

Task 15: Verificar espacio libre del pool y riesgo de fragmentación

cr0x@server:~$ zfs list -o name,used,avail,refer,mountpoint tank
NAME   USED  AVAIL  REFER  MOUNTPOINT
tank  3.15T  8.74T   128K  /tank
cr0x@server:~$ sudo zpool list -o name,size,alloc,free,fragmentation,capacity
NAME  SIZE   ALLOC  FREE   FRAG  CAP
tank  11.9T  3.15T  8.74T   18%  26%

Significado: Fragmentación y capacidad importan. A medida que los pools se llenan y fragmentan, la asignación se complica y la latencia puede subir.

Decisión: Si haces benchmark al 10–30% y producción está al 80%, estás midiendo otra máquina. Planifica pruebas a ocupación realista.

Guion de diagnóstico rápido

Cuando un resultado de benchmark parece incorrecto—o producción está lenta y tratas de reproducirlo—no empieces a cambiar propiedades de ZFS al azar. Haz esto en cambio, en orden. Rápido. Determinista. Aburrido.

Primero: confirma que la prueba es I/O real, no teatro de caché

  1. Revisa zpool iostat mientras corre el benchmark. Si el ancho de banda del pool está cerca de cero, detente.
  2. Revisa tasas de miss en arcstat. Si los misses son bajos, estás probando comportamiento de ARC.
  3. Verifica fio direct=1 y que el tamaño de archivo exceda RAM si se requiere caché fría.

Segundo: determina si el cuello de botella está en discos, CPU o semánticas sync

  1. Ejecuta iostat -x. Si %util es alto y await sube, los dispositivos están saturados.
  2. Ejecuta mpstat. Si %sys es alto y CPUs ocupadas mientras discos no lo están, estás limitado por CPU (checksums, compresión, interrupciones, locking).
  3. Forza comportamiento sync en el dataset (sync=always) para una prueba corta. Si el rendimiento colapsa y la latencia explota, tu cuello de botella real es la ruta sync y/o SLOG.

Tercero: valida supuestos de configuración de ZFS

  1. Revisa topología (mirror/raidz) y ashift.
  2. Confirma propiedades del dataset: recordsize/volblocksize, compression, logbias, primarycache.
  3. Busca trabajos en background: scrub, resilver, TRIM, snapshots enviando/recibiendo.

Si sigues ese orden, usualmente podrás nombrar el cuello de botella en 5–10 minutos. No arreglarlo—nombrarlo. Nombrar es el primer paso para no andar dando palos al azar.

Tres mini-historias corporativas (dolorosamente plausibles)

Incidente causado por una suposición equivocada: “Es NVMe, así que sync irá bien”

Una empresa mediana desplegó un nuevo datastore respaldado por ZFS para máquinas virtuales. Era todo flash. Era caro. Tenía un benchmark hermoso: las escrituras aleatorias “iban genial”, y las gráficas tenían suficientes colores para calificarlas como arte moderno.

Dos semanas después, los inicios de sesión matutinos se convirtieron en un desastre en cámara lenta. Las consolas de VM se congelaban por segundos. Bases de datos dentro de las VMs empezaron a hacer timeouts. El equipo de almacenamiento hizo lo que hacen los equipos bajo presión: reejecutaron el benchmark en el que confiaban, vieron buenos números y declararon que el problema debía ser “la red”.

La suposición equivocada fue simple: “Todo flash significa que las escrituras sync serán rápidas.” Su benchmark usó escrituras async con colas profundas y midió latencia promedio. Producción tenía mucho comportamiento fsync—journaling, metadatos y flushes de guest OS. ZFS trató eso como escrituras síncronas, y sin un SLOG adecuado, la ruta de latencia forzó commits durables que se atascaban en el peor comportamiento del flash.

La solución no fue mágica. Añadieron un SLOG de baja latencia y protegido contra pérdida de energía y volvieron a ejecutar el benchmark con sync=always y percentiles de latencia habilitados. Los números fueron más bajos pero honestos. Las tormentas de inicio matutinas volvieron a ser aburridas, que es el mayor cumplido que le puedes dar a un sistema de almacenamiento.

Una optimización que salió mal: recordsize y “throughput gratis”

Un equipo de analítica quería lecturas de escaneo más rápidas. Alguien leyó que “recordsize más grande = lecturas secuenciales más rápidas”, y muchas veces es cierto. Cambiaron recordsize a 1M en un dataset que contenía tanto archivos parquet de analítica como un servicio de metadatos que gestionaba jobs y registros pequeños.

Los benchmarks mejoraron de inmediato para lecturas a granel. Celebraron. Luego el servicio de metadatos empezó a tener picos de latencia. La base de datos hacía pequeñas actualizaciones aleatorias, y ZFS ahora tenía que gestionar registros grandes para modificaciones pequeñas, causando amplificación read-modify-write y actualizaciones de metadatos extra. El servicio no necesitaba throughput; necesitaba latencia predecible en escrituras pequeñas.

“Lo arreglaron” aumentando iodepth y concurrencia de fio hasta que el benchmark volvió a verse bien. Eso embelleció las gráficas mientras empeoraba el servicio, porque el encolamiento ocultaba latencia transformándola en backlog.

La solución real fue aburrida: separar las cargas. Mantener recordsize 1M para archivos analíticos en un dataset. Poner la base de datos en su propio dataset con recordsize más pequeño y semántica sync apropiada. La gráfica de throughput quedó menos impresionante; la cola de incidentes se redujo. Elige tus trofeos.

Una práctica aburrida pero correcta que salvó el día: medir y documentar el estado de la caché

Una financiera hacía chequeos trimestrales de capacidad y rendimiento antes de ventanas de reporte. No era glamoroso. Tenían un runbook que especificaba: nivel de llenado del pool, tamaño de ARC al inicio, propiedades del dataset, archivos de trabajo de fio y cómo registrar latencia p95/p99. Cada ejecución incluía pruebas “frías” y “calientes”.

Un trimestre, un cambio de firmware en un lote de SSDs introdujo una peculiaridad de latencia bajo ciertas profundidades de cola. Los números de throughput secuencial no se movieron. La latencia promedio de lectura aleatoria parecía bien. Pero p99 saltó de una forma que solo se veía cuando la caché estaba fría y el working set excedía ARC.

Porque tenían líneas base consistentes, la desviación fue obvia. Lo detectaron antes de la ventana de reporting. Revirtieron el firmware y pusieron en cuarentena el lote. Nada de depuración heroica a las 2 a.m., sin compras de emergencia, sin “¿la red?”

Ese equipo nunca ganó un premio a la innovación. Ganaron algo mejor: dormir sin interrupciones.

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

Aquí es donde tus resultados de benchmark van a ser diagnosticados como un servidor enfermo: hechos fríos, sin vibraciones.

1) Síntoma: “Las lecturas son increíblemente rápidas, más que los SSDs”

  • Causa raíz: ARC (o caché del SO) está sirviendo lecturas. El working set cabe en RAM, o repetiste la ejecución y calentaste la caché.
  • Solución: Usa direct=1, incrementa el tamaño del dataset más allá de ARC, registra tasa de misses de ARC y etiqueta explícitamente resultados “caché caliente” vs “caché fría”.

2) Síntoma: “IOPS de escritura aleatoria son geniales, pero las DBs en producción son lentas”

  • Causa raíz: El benchmark usó escrituras async; producción requiere sync (fsync). No hay SLOG, o SLOG débil, o no probaste la ruta sync.
  • Solución: Prueba con sync=always, mide latencia p99, añade/valida SLOG con protección contra pérdida de energía y confirma su uso vía zpool iostat.

3) Síntoma: “El throughput es alto por 30 segundos, luego cae”

  • Causa raíz: Hiciste benchmark de caché y buffering de escritura (TXG) en vez de throughput sostenido del dispositivo. Las ejecuciones cortas también ignoran comportamiento steady-state de asignación.
  • Solución: Ejecuta pruebas más largas, usa runs time_based con suficiente duración y vigila el ancho de banda del pool con zpool iostat 1.

4) Síntoma: “Picos de latencia cada pocos segundos”

  • Causa raíz: Commits de TXG, flushs sync o picos de latencia del SLOG. A veces scheduling de CPU o tormentas de interrupciones.
  • Solución: Captura percentiles de latencia con fio, correlaciónalos con zpool iostat -r -w y revisa comportamiento de CPU/interrupciones. Valida calidad y firmware del SLOG.

5) Síntoma: “Cambiar compresión hace todo más rápido, así que dependeremos de ella”

  • Causa raíz: Los datos de benchmark comprimen; los datos de producción pueden no hacerlo (ya comprimidos/encriptados). La compresión oculta límites de I/O.
  • Solución: Verifica compressratio en datos representativos. Si producción es incomprimible, haz benchmarks con compresión desactivada o con datos realistas.

6) Síntoma: “Un job de fio muestra buenos números; la app real es más lenta”

  • Causa raíz: Desajuste de carga: tamaño de bloque, comportamiento sync, concurrencia, patrón de acceso, número de archivos, mezcla de metadatos.
  • Solución: Construye jobs de fio que se parezcan a la app: mismos tamaños de bloque, misma tasa de fsync, concurrencia similar, conteo de archivos realista y proporciones mixtas de lectura/escritura.

7) Síntoma: “El rendimiento del pool es inconsistente entre ejecuciones”

  • Causa raíz: Cambios en estado de caché, trabajos en background (scrub/resilver), cambios en nivel de llenado, throttling térmico, cambios de governor de CPU.
  • Solución: Controla el entorno: aísla el host, fija el governor, detén tareas de fondo, corre warm-up y registra nivel de llenado y estado de ARC en cada ejecución.

8) Síntoma: “Los IOPS se ven bien pero la latencia p99 es terrible”

  • Causa raíz: El encolamiento oculta el dolor. Un iodepth profundo puede inflar IOPS mientras aumenta la latencia de cola. También posible contención o stalls por sync.
  • Solución: Haz benchmarks con varios valores de iodepth, traza IOPS vs p99 y elige un punto que cumpla tus SLOs de latencia. No optimices solo por IOPS.

Broma #2: Si tu benchmark solo reporta promedios, es básicamente un horóscopo con mejor formato.

Listas de verificación / plan paso a paso

Paso a paso: una sesión de benchmark ZFS repetible (grado producción)

  1. Escribe la pregunta del benchmark (throughput vs IOPS vs latencia, sync vs async, archivo vs zvol).
  2. Registra el entorno: versión de kernel, versión de ZFS, modelo de CPU, RAM, modelos de dispositivo, controlador, estado de firmware.
  3. Confirma topología del pool y ashift (zpool status, zdb -C).
  4. Crea un dataset dedicado para benchmarks y fija propiedades explícitamente (compression, atime, sync, recordsize/primarycache).
  5. Pon el estado del pool: sin scrub/resilver, temperatura estable, governor de CPU estable, ruido mínimo.
  6. Decide modo de caché: frío vs caliente. Documenta tamaño de ARC y misses. Si es frío, usa un working set grande y/o reinicia entre ejecuciones.
  7. Calentamiento: ejecuta una pre-prueba corta para estabilizar asignación y evitar anomalías de “first touch”.
  8. Ejecuta el benchmark el tiempo suficiente para estado estable. Captura salida de fio, además de zpool iostat e iostat -x concurrentemente.
  9. Repite al menos 3 veces por configuración. Si la varianza es alta, tienes ruido o un cuello de botella no determinista.
  10. Cambia una variable (recordsize o compresión o sync o logbias), vuelve a ejecutar la misma suite.
  11. Interpreta usando múltiples capas: fio + zpool iostat + iostat -x + estadísticas de CPU.
  12. Decide según tu SLO: elige la configuración que cumpla objetivos de latencia p99, no la que tenga el mayor throughput pico.

Plantilla de suite de benchmarks: mínima pero honesta

  • Escritura/lectura secuencial: bloques de 1M, 1–4 jobs, iodepth 16, 2 minutos.
  • Lectura aleatoria: bloques de 4k, jobs 4–8, iodepth 16–32, 3 minutos, percentiles.
  • Escritura aleatoria async: bloques de 4k, jobs 4–8, iodepth 8–16, 3 minutos.
  • Escritura aleatoria sync: igual que arriba, pero con sync=always y medir p99/p999.
  • Mixto 70/30 lectura/escritura: 8k o 16k, percentiles de latencia.

Preguntas frecuentes

1) ¿Debo hacer benchmarks con compresión activada o desactivada?

Ambas, pero etiquétalas. La compresión es una característica real que puede mejorar throughput y reducir latencia al hacer menos I/O. Es “engaño” solo cuando tus datos de benchmark comprimen y los datos de producción no.

2) ¿Es aceptable usar dd para benchmarks de ZFS?

Para una prueba rápida secuencial está bien. Para cualquier cosa que involucre latencia, I/O aleatorio, concurrencia o semánticas sync, usa fio y captura percentiles. dd es demasiado propenso a medir caché y readahead por accidente.

3) ¿Qué hace sync=disabled a los resultados del benchmark?

Puede hacer que las escrituras parezcan dramáticamente más rápidas al reconocerlas antes de que sean durables. Eso no es un “truco de tuning”; es cambiar el contrato de durabilidad. Si lo usas en producción, aceptas pérdida de datos en eventos de corte de energía o crash.

4) ¿Qué tamaño deben tener mis archivos de prueba de fio?

Para comportamiento con caché fría: mayores que ARC (a menudo mayores que RAM). Para comportamiento con caché caliente: dimensionados para coincidir con tu working set realista. Siempre indica cuál probaste.

5) ¿Por qué mis números cambian después de que el pool se llena?

La asignación se vuelve más compleja, aumenta la fragmentación y cambia el comportamiento de metaslab. El rendimiento de ZFS al 20% no es el mismo que al 80%. Haz pruebas a capacidad realista si te importa producción.

6) ¿Necesito un dispositivo SLOG?

Sólo si tu carga emite escrituras síncronas y te importa reducir latencia sync. Un SLOG no ayudará el throughput async ni arreglará un pool ya saturado por disco. Es para latencia sync, no para velocidad general.

7) ¿Qué iodepth debo usar en fio?

Usa un rango. Colas poco profundas (iodepth 1–4) muestran latencia y capacidad de respuesta; colas profundas muestran máximo throughput/IOPS pero pueden destruir la latencia de cola. Elige el iodepth que coincida con la concurrencia y SLOs de latencia de tu app.

8) ¿Cómo sé si estoy limitado por CPU en ZFS?

Si los discos no están ocupados (iostat -x %util bajo) pero la latencia de fio sube y mpstat muestra %sys alto o muchos cores al máximo, probablemente estás limitado por CPU: checksums, compresión, interrupciones, cifrado o contención.

9) ¿Debo comparar raidz vs mirrors como si fueran solo “más discos”?

No. Los mirrors suelen tener mejores IOPS aleatorios y latencia; raidz es eficiente en capacidad y para cargas secuenciales, pero la paridad y la asignación pueden afectar escrituras aleatorias pequeñas. Haz benchmarks con el perfil real de carga, especialmente para escrituras aleatorias y comportamiento sync.

10) ¿Puede L2ARC hacer que los benchmarks se vean mejor?

Sí, pero sigue siendo una caché. Si tu carga es de lectura intensiva y reutiliza datos, L2ARC puede ayudar. Si tu carga es mayormente escritura o lecturas en streaming, no. Si haces benchmark sin indicar capas de caché, tus resultados están incompletos.

Siguientes pasos que sí puedes hacer

Si quieres resultados de benchmarking de ZFS que sobrevivan el contacto con producción, haz tres cosas esta semana:

  1. Escribe y controla en versión tus definiciones de job de fio (aunque sean simples) y registra propiedades del dataset junto a los resultados.
  2. Añade una “verificación de verdad” estándar a cada ejecución: zpool iostat + arcstat + iostat -x capturados durante el benchmark. Si las capas no coinciden, el benchmark no cuenta.
  3. Evalúa el comportamiento sync con percentiles de latencia, incluso si crees que no lo necesitas. Sync es donde los sistemas dejan de ser rápidos y empiezan a ser honestos.

ZFS es capaz de un rendimiento excelente. También es capaz de producir benchmarks espectacularmente engañosos si lo permites. Las reglas anteriores no pretenden ganar; pretenden evitar sorpresas posteriores—cuando la sorpresa viene con un pager.

← Anterior
Punto de montaje ZFS: La trampa de montaje que hace ‘desaparecer’ los datasets
Siguiente →
El daemon de Docker no arranca: lea este registro primero (y luego arréglelo)

Deja un comentario