Ajuste de pools ZFS solo con HDD: obtener rendimiento sin SSDs

¿Te fue útil?

Tienes un montón de discos giratorios, sin presupuesto para SSDs, y usuarios que piensan que “almacenamiento” es un botón que se pulsa para acelerar la aplicación.
Mientras tanto, tu pool ZFS hace exactamente lo que permite la física: I/O aleatorio lento, I/O secuencial decente y drama ocasional durante los scrubs.

Esta es una guía de campo para hacer que ZFS solo con HDD parezca sorprendentemente competente—sin engañarte. Afinaremos lo afinable, evitaremos
las trampas que parecen rápidas hasta que te arruinan la semana, y construiremos un músculo de diagnóstico que funcione a las 3 a.m.

El modelo mental: en qué son buenos los pools con HDD

Afinar ZFS en pools solo con HDD es principalmente el arte de no pedirle al disco lo que no puede dar. Los discos giratorios son fantásticos en ancho de banda secuencial y
terribles en IOPS aleatorios. ZFS es fantástico en corrección y flexibilidad, y bastante bueno en rendimiento—siempre que alineas tu carga de trabajo
con la geometría del pool y el comportamiento de escritura/lectura de ZFS.

Una regla para tatuar en tu runbook

Para pools HDD, no “optimizas para IOPS”. Reduces I/O aleatorio, aumentas la secuencialidad efectiva y previenes la amplificación de escritura evitable.
Si tu carga es realmente escrituras aleatorias en bloques pequeños, la optimización más honesta es cambiar la carga o comprar SSDs. Todo lo demás es
negociar con la física.

Por qué ZFS puede sentirse lento en HDDs (y por qué a menudo es tu culpa)

  • Copy-on-write significa que las sobreescrituras se convierten en nuevas escrituras, con actualizaciones de metadatos también. Aparecen fragmentación y escrituras dispersas si haces muchas actualizaciones aleatorias.
  • Transaction groups (TXGs) agrupan cambios y hacen flush periódicamente. Esto es bueno para throughput; puede ser malo para la latencia si malinterpretas el comportamiento de sync.
  • Checksumming añade trabajo de CPU y obliga a lecturas de bloque completo en escrituras parciales en algunos casos (read-modify-write).
  • RAIDZ es eficiente en capacidad, pero las escrituras aleatorias pequeñas pueden desencadenar sobrecarga de paridad y penalizaciones RMW.

Chiste #1: Los HDDs son como un manager que lee email—asombrosos procesando un hilo largo, y catastróficamente lentos cuando los interrumpes cada 8 milisegundos.

“Velocidad” significa varias cosas

La mayoría de errores de afinado vienen de optimizar la métrica equivocada:

  • Throughput (MB/s) para backups, medios, object storage, grandes lecturas ETL.
  • IOPS para cargas con mucha metadata, almacenamiento de correo, escrituras aleatorias de VMs.
  • Latencia para bases de datos, escrituras sync de VMs, NFS con semántica sync.

A menudo puedes mejorar throughput y también reducir latencia cola evitando patrones patológicos (pequeñas escrituras sync, recordsize mal dimensionado,
RAIDZ demasiado ancho, scrubs agresivos en hora pico). Pero no puedes hacer que ocho HDDs se comporten como ocho SSDs. La victoria es hacer que se comporten
como una matriz de ocho discos bien alimentada en lugar de una bolsa de búsquedas tristes.

Hechos e historia que realmente importan

Afinar almacenamiento es más fácil cuando sabes qué ideas “antiguas” siguen influyendo en el comportamiento moderno. Aquí hay hechos cortos y concretos que siguen pagando la cuenta:

  1. ZFS nació en Sun Microsystems a mediados de los 2000 para acabar con la corrupción silenciosa y simplificar la administración; el rendimiento se diseñó para ser predecible bajo restricciones de corrección.
  2. Copy-on-write explica por qué ZFS siempre puede validar datos con checksums, pero también significa que las sobreescrituras aleatorias tienden a fragmentarse con el tiempo.
  3. RAIDZ se diseñó para evitar el “write hole” que RAID-5 clásico puede sufrir en cortes de energía; la consistencia de la paridad es parte del diseño, no un añadido.
  4. El ARC (Adaptive Replacement Cache) evolucionó para superar cachés LRU simplistas equilibrando recencia y frecuencia; en pools HDD, la efectividad del ARC suele ser tu mayor palanca de rendimiento “gratis”.
  5. Los discos con sector de 4K cambiaron el panorama y ashift existe porque el sistema no siempre puede confiar en el tamaño de sector reportado; elegirlo mal puede penalizar el rendimiento permanentemente.
  6. LZ4 se volvió la compresión favorita porque suele ser más rápida que el disco al throughput típico de HDD; a menudo aumenta el ancho de banda efectivo al escribir menos.
  7. El ZIL (intent log) no es una caché de escritura; existe para confirmar sincronicidad de forma segura. Sin un dispositivo SLOG dedicado, el ZIL vive en los HDDs y las escrituras sync pueden ser el rey de la latencia.
  8. Los scrubs no son opcionales en la cultura ZFS porque los checksums solo detectan corrupción cuando lees; los scrubs fuerzan lecturas para encontrar y sanar errores latentes.
  9. RAIDZ ancho se popularizó con discos grandes por capacidad, pero operativamente aumenta el tiempo de resilver y el radio de explosión de mal rendimiento durante reconstrucciones—especialmente en pools HDD ocupados.

Una idea para recordar: La esperanza no es una estrategia; mide el sistema que tienes y cambia una cosa a la vez — idea parafraseada de Gene Kranz (disciplina de operaciones de misión).

Decisiones de diseño del pool que lo cambian todo

Si estás afinando un pool existente, algunas decisiones de diseño están fijadas. Pero aún necesitas entender lo que construiste, porque muchos “ajustes”
son en realidad “elegiste RAIDZ2 para una granja de VMs” problemas.

Mirrors vs RAIDZ en HDDs

Si tu carga es sensible a IOPS (VMs, bases de datos, tormentas de metadata), los mirrors ganan. Y no por poco. Los mirrors te dan más actuadores independientes
(spindles) para lecturas aleatorias y a menudo mejor comportamiento de escritura. RAIDZ ofrece eficiencia de capacidad y gran throughput secuencial,
pero lo paga con sobrecarga de paridad y read-modify-write en escrituras pequeñas.

  • Mirrors: mejores IOPS de lectura aleatoria, escritura aleatoria decente, resilver rápido (solo copia bloques usados).
  • RAIDZ2/3: más TB utilizables por disco, buen streaming secuencial, comportamiento de escrituras pequeñas más complejo, resilver puede ser pesado.

No hagas vdevs demasiado anchos

“Vdevs más anchos son más rápidos” es cierto para throughput secuencial y a menudo falso para latencia y comportamiento en rebuilds. Un RAIDZ2 de 12 columnas puede
transmitir como un campeón, y luego convertirse en una bestia frágil y ocupada durante el resilver mientras tus aplicaciones compiten por búsquedas.

Alineación de recordsize y geometría vdev

ZFS escribe bloques de tamaño variable hasta el recordsize (datasets) o volblocksize (zvols). En RAIDZ, cada bloque se divide
entre columnas más paridad. Si tus tamaños de bloque no encajan con la geometría RAIDZ, puedes acabar con más operaciones de las esperadas y más escrituras de media-franja.

Para pools solo HDD, la meta pragmática es: escribir menos, bloques más grandes y bien comprimidos; evitar reescribirlos frecuentemente; mantener metadata y
caminos de escritura aleatoria pequeños bajo control.

ashift: eliges una vez, sufres para siempre

ashift establece el exponente del tamaño de sector del pool. En la práctica casi siempre quieres ashift=12 (4K). A veces quieres
ashift=13 (8K) para algunos discos avanzados o para reducir sobrecarga con dispositivos de gran sector. Si elegiste ashift=9 en
discos 4K, te compraste un dolor permanente de read-modify-write.

Si estás atrapado con un ashift malo, la solución de “afinamiento” es migrar a un pool nuevo. ZFS es educado; no reescribirá los cimientos del pool solo porque ahora te arrepientes.

Ajuste de datasets y zvols (recordsize, compresión, sync)

La mayoría de las mejoras en pools solo HDD vienen de establecer propiedades por dataset según la carga. ZFS te permite afinar por dataset, así que deja de pensar “a nivel de pool”
y empieza a pensar “este dataset guarda discos de VM, aquel guarda backups, este guarda logs.”

recordsize: deja de hacer trabajar de más al disco

recordsize afecta cómo ZFS trocea los datos de archivo. Registros más grandes suelen mejorar throughput secuencial y eficiencia de compresión.
Registros más pequeños pueden reducir amplificación de lectura para lecturas aleatorias y reducir el tamaño de reescritura para pequeñas actualizaciones—a veces.

Predeterminados con opinión:

  • Backups, medios, objetos grandes: recordsize=1M suele ser excelente en pools HDD.
  • Compartidos de archivos generales: el predeterminado 128K está bien; cambia solo con evidencia.
  • Bases de datos: a menudo 16K o 32K, pero prueba con el tamaño de página y patrón de acceso de tu BD.
  • Imágenes de VM como archivos: depende; muchas funcionan mejor con 64K o 128K más compresión, pero las cargas de escritura aleatoria pueden seguir sufriendo en RAIDZ.

volblocksize para zvols: fíjalo al crear, no después

Si presentas iSCSI o usas zvols para discos de VM, volblocksize es tu tamaño de bloque. No puede cambiarse después de crear el zvol.
Coincide con el sistema de archivos invitado/tamaño de página de la BD cuando sea posible (a menudo 8K o 16K). Muy pequeño aumenta metadata y operaciones de I/O; muy grande aumenta amplificación de escritura para pequeñas actualizaciones aleatorias.

Compresión: lo que más se parece a un almuerzo gratis

En HDDs, la compresión suele ser una ganancia porque reduce I/O físico. lz4 es típicamente la base correcta. La advertencia es la CPU: si estás corto de CPU o usas algoritmos más pesados, puedes cambiar el cuello de botella.

sync: donde va a morir la latencia

Las escrituras sincrónicas requieren confirmación de durabilidad. Sin SSDs (sin SLOG dedicado), tus escrituras sync se comprometen al ZIL en el pool, lo que
significa movimientos de cabeza y latencia rotacional. Las aplicaciones que hacen muchas escrituras sync pequeñas harán que un pool HDD parezca que funciona mal.

Verdad dura: activar sync=disabled hace que los benchmarks luzcan bien y que los postmortems sean caros.

atime: deja de escribir porque alguien leyó un archivo

En pools HDD, atime=on puede crear churn de escritura innecesario para datasets de solo lectura frecuente. Desactívalo para la mayoría de cargas de servidor
a menos que tengas un requisito concreto para tiempos de acceso.

xattr y dnodesize: la metadata importa en los discos giratorios

Las cargas pesadas en metadata pueden ser brutales en HDDs. Ajustes como xattr=sa y dnodesize=auto pueden reducir I/O de metadata para algunas cargas,
pero no son magia universal. La lección mejor: identifica tormentas de metadata temprano y aíslalas en datasets que puedas afinar.

ARC, presión de memoria y por qué “más RAM” no es un mito

Si no tienes SSDs, la RAM es tu capa de rendimiento. El ARC es donde van las lecturas calientes para evitar búsquedas en disco. En pools HDD, un ARC sano puede ser
la diferencia entre “suficientemente rápido” y “¿por qué ls es lento?”.

Tamaño del ARC: no dejes sin recursos al OS, ni a ZFS

El tamaño correcto del ARC depende de tu plataforma y carga. El tamaño incorrecto es fácil: demasiado grande y haces swap; demasiado pequeño y haces thrash en disco.
Hacer swap en un sistema que sirve I/O es como poner arena en la caja de cambios porque quieres más tracción.

Sabe qué hay en ARC: datos vs metadata

Los pools HDD a menudo se benefician de cachear metadata (entradas de directorio, bloques indirectos, dnodes). Si los misses de metadata fuerzan búsquedas en disco,
tu carga de “lectura aleatoria” se convierte en “lectura aleatoria de metadata” más “lectura aleatoria de datos”. Eso es un dos por uno que nadie pidió.

Nota especial sobre dedup

Dedup en pools solo HDD suele ser una pesadilla de rendimiento a menos que tengas un caso muy específico y mucha RAM para las tablas de dedup.
Si quieres ahorro de espacio, empieza por la compresión. Si aún quieres dedup, trae calculadora y un plan de reversión.

Prefetch, lecturas secuenciales y cargas de trabajo streaming

Prefetch es ZFS tratando de ayudar leyendo por adelantado cuando detecta acceso secuencial. En pools HDD, el acceso secuencial es tu lugar feliz.
Cuando el prefetch funciona, el throughput sube y la latencia se suaviza. Cuando falla (común con algunos patrones de VM), puede desperdiciar ancho de banda y
expulsar entradas útiles del ARC.

El enfoque de afinado no es “deshabilitar prefetch porque alguien en un foro lo dijo”. Es: detectar si la carga es realmente secuencial, luego probar.
Si puedes reorganizar I/O para que sea más secuencial—bloques más grandes, menos puntos sync, escrituras por lotes—deberías hacer eso antes de tocar perillas de prefetch.

Scrub y resilver: seguridad sin destrozar el rendimiento

Scrubs y resilvers son los momentos en que tu pool deja de ser un sistema de almacenamiento y se convierte en un proyecto de almacenamiento. Los HDDs tienen ancho de banda y
IOPS finitos. Si la producción está ocupada y comienza un resilver, algo perderá. Tu trabajo es decidir qué pierde y hacerlo predecible.

Programar scrubs es ajuste de rendimiento

Los scrubs son esenciales. Pero hacer scrubs al mediodía en un servidor NFS ocupado es la forma de aprender cómo suenan tus ejecutivos cuando descubren latencia.
Programa scrubs fuera de pico y monitoriza su duración a lo largo del tiempo. El aumento de duración es un olor a problema: fragmentación, crecimiento de datos, discos fallando,
o un cambio de carga.

El comportamiento de resilver difiere por el diseño

Los mirrors suelen resilver más rápido porque solo copian bloques asignados. RAIDZ ha mejorado en los años (resilver secuencial), pero todavía estresa el pool porque la reconstrucción necesita lecturas de muchos discos y escrituras en uno, además de actualizaciones de metadata.

Chiste #2: Un resilver durante horas pico es lo más parecido que tiene el almacenamiento a un simulacro en vivo—excepto que el incendio es tu cola de tickets.

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

Estas son tareas operativas reales que puedes ejecutar en un host ZFS (Linux o variantes illumos con herramientas similares). Cada tarea incluye lo que te dice la salida
y qué decisión tomar. Usa un bloc de notas. No “afines” de memoria.

Task 1: Identificar la salud del pool y rutas lentas obvias

cr0x@server:~$ zpool status -v tank
  pool: tank
 state: ONLINE
status: Some supported features are not enabled on the pool.
action: Upgrade the pool to enable all features.
  scan: scrub repaired 0B in 05:12:41 with 0 errors on Sun Dec 22 03:10:14 2025
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          raidz2-0                  ONLINE       0     0     0
            wwn-0x5000c500a1b2c3d4  ONLINE       0     0     0
            wwn-0x5000c500a1b2c3d5  ONLINE       0     0     0
            wwn-0x5000c500a1b2c3d6  ONLINE       0     0     0
            wwn-0x5000c500a1b2c3d7  ONLINE       0     0     0
            wwn-0x5000c500a1b2c3d8  ONLINE       0     0     0
            wwn-0x5000c500a1b2c3d9  ONLINE       0     0     0

errors: No known data errors

Significado: La salud está bien; el scrub duró ~5 horas; sin errores. “Some supported features are not enabled on the pool.” no es un incendio de rendimiento por sí mismo.
Decisión: Si los scrubs se están alargando cada mes, investigarás fragmentación y rendimiento de discos. De lo contrario, sigue con lo siguiente.

Task 2: Ver latencia en tiempo real e identificar si las lecturas o escrituras duelen

cr0x@server:~$ zpool iostat -v tank 2 5
              capacity     operations     bandwidth
pool        alloc   free   read  write   read  write
----------  -----  -----  -----  -----  -----  -----
tank        48.2T  21.6T    120    980  12.8M  38.4M
  raidz2-0  48.2T  21.6T    120    980  12.8M  38.4M
    wwn-...     -      -     18    155  2.1M   6.2M
    wwn-...     -      -     20    160  2.2M   6.4M
    wwn-...     -      -     16    148  1.9M   6.0M
    wwn-...     -      -     19    158  2.1M   6.3M
    wwn-...     -      -     17    152  2.0M   6.1M
    wwn-...     -      -     18    154  2.1M   6.1M
----------  -----  -----  -----  -----  -----  -----

Significado: Las escrituras dominan las operaciones; el bandwidth es moderado. Si la carga es lenta, probablemente está limitada por latencia/IOPS, no por throughput.
Decisión: A continuación, revisa escrituras sync y patrones de I/O por proceso; también valida que la app no esté haciendo pequeñas tormentas de fsync.

Task 3: Comprobar propiedades de datasets que más deciden el rendimiento

cr0x@server:~$ zfs get -o name,property,value -r recordsize,compression,atime,sync tank
NAME          PROPERTY     VALUE
tank          recordsize   128K
tank          compression  lz4
tank          atime        off
tank          sync         standard
tank/vm       recordsize   128K
tank/vm       compression  lz4
tank/vm       atime        off
tank/vm       sync         standard
tank/backup   recordsize   1M
tank/backup   compression  lz4
tank/backup   atime        off
tank/backup   sync         disabled

Significado: El dataset de backup está afinado para throughput pero tiene sync=disabled, lo cual es un riesgo de pérdida de datos para cualquier cosa que reclame durabilidad.
Decisión: Si ese dataset es realmente “staging de backups recreables”, tal vez aceptable; si no, ponlo de vuelta en sync=standard y arregla el escritor para agrupar.

Task 4: Verificar ashift (alineación de sector) en cada vdev

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

Significado: ashift=12 es bueno para discos modernos de 4K.
Decisión: Si ves ashift: 9 en discos 4K, planifica una migración. No pierdas tiempo en micro-ajustes.

Task 5: Medir ratio de aciertos del ARC y presión de memoria

cr0x@server:~$ arcstat 1 5
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
12:10:01   980    62      6    40   4%    22   2%    10   1%   22G   24G
12:10:02  1012    74      7    51   5%    23   2%    12   1%   22G   24G
12:10:03   990    69      6    44   4%    25   3%     9   1%   22G   24G
12:10:04  1005    80      8    55   5%    25   2%    13   1%   22G   24G
12:10:05   970    58      5    37   4%    21   2%     9   1%   22G   24G

Significado: Tasa de misses ~5–8% es decente; ARC está cerca de su objetivo. Si miss% es alto y los discos están ocupados, estás haciendo más I/O físico del necesario.
Decisión: Si tienes RAM libre, aumenta el máximo del ARC (dependiendo de la plataforma). Si no, prioriza cache de metadata mediante separación de cargas o reduce el working set (snapshots, proliferación de datasets, etc.).

Task 6: Identificar si la carga está limitada por escrituras sync

cr0x@server:~$ grep -E "zil|sync" /proc/spl/kstat/zfs/zil
5 1 0x01 107 4080 173968149 2130949321
zil_commit_count            4    84211
zil_commit_writer_count     4    84211
zil_itx_count               4    5128840
zil_itx_indirect_count      4    120
zil_itx_indirect_bytes      4    983040

Significado: Alto zil_commit_count implica transacciones sync frecuentes.
Decisión: Si los usuarios se quejan de latencia y ves muchos commits ZIL, investiga qué dataset recibe I/O sync y qué proceso llama a fsync().
La solución a menudo está en la capa de la app/BD, no en ZFS.

Task 7: Encontrar qué procesos generan I/O

cr0x@server:~$ iotop -oPa
Total DISK READ: 12.31 M/s | Total DISK WRITE: 41.22 M/s
  PID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
 8421 be/4  postgres  1.12 M/s   9.65 M/s    0.00 %  23.11 % postgres: wal writer
 9102 be/4  qemu      4.88 M/s  18.33 M/s    0.00 %  31.74 % qemu-system-x86_64 -drive file=/tank/vm/vm01.img
 2210 be/4  root      0.00 B/s   6.10 M/s    0.00 %   9.12 % rsync -a /staging/ /tank/backup/

Significado: WAL de PostgreSQL y QEMU son intensivos en escritura. WAL suele ser sensible a sync.
Decisión: Para BD, considera afinado a nivel de dataset (recordsize, ubicación de logs) y ajustes en la BD (batching de commits). Para VMs,
considera seriamente mirrors para el dataset de VM incluso si el resto del pool es RAIDZ.

Task 8: Comprobar fragmentación y margen de capacidad

cr0x@server:~$ zpool list -o name,size,alloc,free,cap,frag,health tank
NAME  SIZE   ALLOC  FREE  CAP  FRAG  HEALTH
tank  69.8T  48.2T  21.6T  69%   38%  ONLINE

Significado: 69% lleno, 38% fragmentado. No es catastrófico, pero está en tendencia hacia “las escrituras aleatorias empeorarán”.
Decisión: Si la CAP supera ~80% en pools HDD, espera un acantilado de rendimiento. Planea expansión, borrado o migración antes de llegar allí.

Task 9: Verificar ratio de compresión y si reduces I/O

cr0x@server:~$ zfs get -o name,used,compressratio -r tank | head
NAME        USED   COMPRESSRATIO
tank        48.2T  1.52x
tank/vm     18.4T  1.18x
tank/backup 22.1T  1.94x

Significado: Los backups comprimen bien (bueno). El dataset VM comprime mal (normal para imágenes OS ya comprimidas).
Decisión: Mantén lz4 a menos que la CPU sea el cuello de botella. Si la compresión de VM está cerca de 1.00x y la CPU está caliente, puedes considerar desactivar la compresión solo para ese dataset.

Task 10: Inspeccionar la propiedad sync por dataset y arreglar atajos peligrosos

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

Significado: Backup es inseguro para semánticas sync. Algunas herramientas de backup dependen de fsync para corrección.
Decisión: Si no puedes justificarlo en una revisión de riesgos, cámbialo:

cr0x@server:~$ sudo zfs set sync=standard tank/backup

Task 11: Determinar si estás haciendo I/O aleatorio en bloques pequeños (el asesino de HDD)

cr0x@server:~$ sudo fio --name=randread --filename=/tank/testfile --size=4G --direct=1 --rw=randread --bs=4k --iodepth=32 --runtime=20 --time_based
randread: (g=0): rw=randread, bs=(R) 4096B-4096B, (W) 4096B-4096B, ioengine=psync, iodepth=32
fio-3.33
randread: IOPS=420, BW=1.64MiB/s (1.72MB/s)(32.8MiB/20001msec)
  lat (usec): min=210, max=185000, avg=76000.15, stdev=22100.42

Significado: Lecturas aleatorias 4K son brutales: unos pocos cientos de IOPS y latencia cola desagradable. Eso es normal en HDDs.
Decisión: Si tu producción se parece a esto, necesitas cambios arquitectónicos: mirrors, más spindles, más RAM/ARC,
o hacer I/O menos aleatorio (batching, bloques más grandes, capa de caché fuera de ZFS).

Task 12: Determinar el cuello de botella de throughput secuencial

cr0x@server:~$ sudo fio --name=seqread --filename=/tank/testfile --size=8G --direct=1 --rw=read --bs=1M --iodepth=8 --runtime=20 --time_based
seqread: (g=0): rw=read, bs=(R) 1024KiB-1024KiB, (W) 1024KiB-1024KiB, ioengine=psync, iodepth=8
fio-3.33
seqread: IOPS=620, BW=620MiB/s (650MB/s)(12.1GiB/20003msec)
  lat (msec): min=2, max=52, avg=12.9, stdev=4.2

Significado: Las lecturas secuenciales son fuertes. Así que si tu app es lenta, probablemente hace I/O no secuencial o fuerza la latencia sync.
Decisión: Ajusta recordsize y patrones de trabajo hacia I/O secuencial donde sea posible (escrituras en streaming, I/O más grandes, menos fsync).

Task 13: Comprobar latencia a nivel de disco para encontrar un actor lento

cr0x@server:~$ iostat -x 2 3
avg-cpu:  %user %nice %system %iowait  %steal %idle
           6.12  0.00    2.20   18.40    0.00  73.28

Device            r/s     w/s   r_await   w_await  aqu-sz  %util
sda              9.20   45.10    12.40    18.70    1.22   78.0
sdb             10.10   44.90    13.20    19.10    1.24   79.4
sdc              9.40   45.20    12.10    17.90    1.18   77.9
sdd              8.90   44.70    61.30    88.10    5.90   99.8

Significado: Un disco (sdd) tiene await mucho peor y está saturado al 99.8% util. Eso puede arrastrar todo el vdev.
Decisión: Saca estadísticas SMART y considera reemplazo proactivo. Un “disco lento” es un modo real de fallo, no superstición.

Task 14: Comprobar SMART por sectores realocados y pendientes

cr0x@server:~$ sudo smartctl -a /dev/sdd | egrep -i "Reallocated_Sector_Ct|Current_Pending_Sector|Offline_Uncorrectable|SMART overall"
SMART overall-health self-assessment test result: PASSED
  5 Reallocated_Sector_Ct   0x0033   098   098   010    Pre-fail  Always       12
197 Current_Pending_Sector  0x0012   100   100   000    Old_age   Always       4
198 Offline_Uncorrectable   0x0010   100   100   000    Old_age   Offline      4

Significado: “PASSED” es una comodidad sin valor; sectores pendientes e in-correctables son malos. Este disco causará reintentos y latencia.
Decisión: Reemplaza el disco, luego resilver fuera de pico, y monitoriza contadores de errores durante la operación.

Task 15: Verificar si los snapshots están inflando metadata y working set

cr0x@server:~$ zfs list -t snapshot -o name,used,refer,creation | head
NAME                          USED  REFER  CREATION
tank/vm@auto-2025-12-01        28G  18.4T  Mon Dec  1 02:00 2025
tank/vm@auto-2025-12-02        31G  18.4T  Tue Dec  2 02:00 2025
tank/vm@auto-2025-12-03        33G  18.4T  Wed Dec  3 02:00 2025

Significado: Muchas snapshots de VM con “USED” creciente indican churn. Ese churn puede aumentar fragmentación y presión sobre ARC.
Decisión: Aplicar retención. Para pools HDD, trata la proliferación de snapshots como un bug de rendimiento, no solo un problema de capacidad.

Manual de diagnóstico rápido

Cuando alguien dice “el almacenamiento está lento”, no tienes tiempo para un debate filosófico sobre patrones de I/O. Necesitas un embudo rápido para localizar
el cuello de botella: disco, comportamiento de ZFS, memoria o semántica de la aplicación.

Primero: confirma si es problema de pool o del host

  1. Comprueba salud del pool y actividad actual.
    Ejecuta zpool status. Si hay un scrub/resilver activo, ese es el titular. Si hay errores de checksum, detén el afinado y empieza respuesta a incidentes.
  2. Revisa iowait de CPU y swapping.
    Si el host hace swap o la CPU está saturada, los síntomas de almacenamiento pueden ser secundarios. Alto iowait suele significar que los discos son el cuello de botella, pero también puede ser paradas por escrituras sync.

Segundo: determina si estás limitado por latencia o throughput

  1. Ejecuta zpool iostat -v 2.
    Si las ops son altas y el ancho de banda es bajo, está limitado por IOPS/latencia (dolor clásico de HDD).
  2. Ejecuta iostat -x 2.
    Busca un disco único con await/%util altos. Un disco cojo puede hacer que un vdev parezca un fallo de diseño.

Tercero: busca tormentas de escrituras sync y de metadata

  1. Mira conteos de commits ZIL e identifica los principales escritores.
    Si ves mucha actividad ZIL y un escritor BD/WAL arriba, tu “almacenamiento lento” es semántica sync encontrando latencia de HDD.
  2. Revisa estadísticas ARC.
    Una mala tasa de aciertos más discos ocupados sugiere un working set mayor que la RAM o mala localidad; necesitarás cambios de carga o más memoria.
  3. Revisa fragmentación y capacidad.
    CAP alto y FRAG en aumento son predictores clásicos de “esto estaba bien el año pasado”.

Qué decides desde el playbook

  • Si hay un scrub/resilver en curso: limitar/programar; comunicar; no “afines” a mitad de reconstrucción a menos que disfrutes el caos.
  • Si un disco está lento: reemplázalo; deja de culpar a ZFS por un HDD moribundo.
  • Si las escrituras sync dominan: arregla el batching en la app; aisla el dataset; no reacciones poniendo sync=disabled a la ligera.
  • Si los misses ARC son altos: añade RAM o reduce el working set; separa datasets; arregla retención de snapshots.
  • Si simplemente haces I/O aleatorio 4K en RAIDZ: el “ajuste” es cambiar el diseño (mirrors) o bajar expectativas.

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

1) “Las escrituras son lentas y con picos; los gráficos parecen una sierra”

Causa raíz: Flush de TXG + ráfagas de escrituras sync + latencia de HDD. A menudo empeorado por una app que hace fsync con frecuencia.

Solución: Identifica el dataset y proceso pesado en sync. Aumenta batching en la app; ajusta commits en la BD; aísla en un dataset con recordsize apropiado; mantiene sync=standard salvo que aceptes formalmente riesgo de pérdida de datos.

2) “Las lecturas aleatorias son terribles; incluso un ls en un directorio pequeño tarda”

Causa raíz: Misses de metadata en el ARC, o metadata fragmentada por churn de snapshots y pequeñas actualizaciones aleatorias.

Solución: Asegura suficiente RAM; reduce el número de snapshots; considera xattr=sa para datasets relevantes; mantén CAP por debajo de ~80%; separa cargas pesadas en metadata en datasets y revisa recordsize.

3) “Después de llenar el pool por encima del 85%, todo se volvió más lento”

Causa raíz: Mapas de espacio, presión del asignador, fragmentación; las búsquedas en HDD aumentan; metaslabs se vuelven restringidos.

Solución: Libera espacio agresivamente; añade capacidad; migra a un pool más grande; deja de operar “cerca del full” como estado estable. Trata el headroom como un requisito de rendimiento.

4) “Los scrubs ahora duran una eternidad y los usuarios se quejan durante los scrubs”

Causa raíz: Crecimiento del dataset, fragmentación, un disco lento, o scrubs programados en horas pico.

Solución: Mueve la ventana de scrub; checa latencia por disco; reemplaza discos lentos; considera reducir ancho del pool en diseños futuros; monitoriza la duración del scrub.

5) “Deshabilitamos la compresión para ‘reducir CPU’ y el rendimiento empeoró”

Causa raíz: Aumentaste I/O físico en HDDs; los discos volvieron a ser el cuello de botella.

Solución: Usa compression=lz4 como base; mide CPU. Si la CPU es realmente el cuello, reduce otros costes de CPU primero (cifrado, checksums pesados) antes de renunciar a la reducción de I/O.

6) “El almacenamiento de VM en RAIDZ es impredecible bajo carga”

Causa raíz: I/O de VM suele ser escrituras pequeñas aleatorias + comportamiento similar a sync desde sistemas invitados e hipervisores. RAIDZ amplifica el dolor.

Solución: Usa mirrors para vdevs de VM; ajusta volblocksize; reduce frecuencia de fsync en guests donde sea seguro; considera pool/dataset separado para VMs con políticas más estrictas.

7) “Un cliente va lento, otros están bien”

Causa raíz: Problemas de red, semántica sync en el lado cliente, o un mismatch de propiedades del dataset.

Solución: Verifica propiedades del dataset para esa share/volumen; compáralo con un dataset conocido bueno; revisa opciones de montaje del cliente y comportamiento de la app.

Tres micro-historias corporativas desde las trincheras

Incidente por una asunción equivocada: “¿ZIL es una caché, verdad?”

Una empresa mediana ejecutaba una plataforma analítica interna en un pool ZFS solo HDD. Todo estaba “bien” hasta que un nuevo equipo desplegó un servicio
que escribía pequeños estados con semántica síncrona. La latencia pasó de tolerable a absurda. El ingeniero on-call vio que el ancho de banda de disco estaba
bajo y concluyó que el pool estaba infrautilizado. “Tenemos amplio headroom,” dijo, mirando gráficos MB/s como si fueran tabletas de verdad.

Activaron sync=disabled en el dataset para “probar”. El servicio se aceleró de inmediato. Los tickets cesaron. El cambio quedó.
Nadie lo documentó. Así es como siempre empieza.

Semanas después, un evento de energía tumbó el rack. Los sistemas volvieron. El servicio volvió también—excepto que parte del estado quedó inconsistente, y tomó
tiempo descubrir qué faltaba porque la app creía tener commits durables. La revisión post-mortem fue incómoda en un sentido muy corporativo: muchas “mejoras de proceso”,
poco nombrar el error técnico real.

La asunción equivocada no fue “sync=disabled es inseguro.” Eso se sabe. La asunción fue creer que el ZIL se comporta como una caché de escritura que puedes ignorar.
En realidad, ZIL existe para que las semánticas sync sean ciertas. En HDDs, eso significa que la latencia rotacional forma parte de tu SLA.

La solución fue aburrida: mover el estado pesado en sync a otra capa de almacenamiento (eventualmente SSDs), y mientras tanto reducir fsync mediante batching y colas.
Además añadieron una política simple: cualquier cambio en propiedades de dataset requiere un ticket con evaluación de riesgo. No los hizo más rápidos. Los hizo menos frágiles.

Optimización que salió mal: “Reduciremos recordsize en todo”

Otra organización operaba ZFS para carga mixta: compartidos de archivos, targets de backup y algunas imágenes de VM. Notaron acceso aleatorio lento en pico y
encontraron consejos en línea sugiriendo recordsize menor para “mejor rendimiento.” Sin perfilar, pusieron recordsize=16K recursivamente en todo el pool.

El efecto inmediato pareció positivo para una carga: una pequeña BD en NFS vio latencia de lectura aleatoria ligeramente mejor. Luego todo lo demás empeoró.
Los backups tardaron más. La CPU subió. Las operaciones de metadata aumentaron. La efectividad del ARC cayó porque la caché tuvo que rastrear muchos más bloques.
Los scrubs tardaron más porque había más bloques que recorrer, y el pool estuvo ocupado más horas.

El remate llegó un mes después: la fragmentación subió, la CAP escaló, y los usuarios empezaron a sufrir pausas intermitentes en transferencias grandes.
El equipo de almacenamiento entró en el clásico bucle: “ajustar más” para arreglar el ajuste que rompió cosas. Casi desactivaron la compresión para “ahorrar CPU,” lo que habría sido la segunda herida autoinfligida.

La recuperación fue metódica. Revirtieron recordsize en backups y compartidos a 128K y 1M donde correspondía, dejando el pequeño recordsize solo en la BD.
Separaron el almacenamiento de VM en su propio dataset y más tarde en un pool mirror. El rendimiento se estabilizó.

Lección: recordsize es un bisturí, no un rodillo. Si lo aplicas a todo el pool, tendrás consecuencias en todo el pool.

Práctica aburrida pero correcta que salvó el día: “Tendencia de duración de scrub”

Una compañía del mundo financiero (de las que aman hojas de cálculo y odian sorpresas) ejecutaba un gran pool RAIDZ2 HDD para archivos de cumplimiento y almacenamiento interno.
No tenían SSDs. Tampoco ilusiones sobre rendimiento. Su líder de almacenamiento impuso dos hábitos: scrubs programados fuera de pico y duración de scrub registrada cada mes en un dashboard simple.
No necesitaban observabilidad fancy.

En meses, la duración del scrub se fue alargando. No dramáticamente, pero lo suficiente para que la línea de tendencia fuera evidente. Nadie reportaba problemas todavía.
Ese es el punto: quieres encontrar problemas cuando los usuarios duermen.

Investigaron. La latencia por disco mostró que un drive tenía await más alto pero sin errores duros. SMART mostraba sectores pendientes en aumento. Aún reportaba “PASSED” en salud general.
Lo reemplazaron proactivamente en una ventana planificada, resilverearon en silencio y los tiempos de scrub volvieron a la línea base anterior.

Semanas después, un disco similar en otro chasis falló abiertamente. Ese equipo sufrió un incidente ruidoso y un resilver largo bajo carga.
La compañía financiera no. Su práctica “aburrida”—tendenciar la duración de scrub y checar latencia por disco—se pagó sin medianoche heroica.

Listas de verificación / plan paso a paso

Plan paso a paso de afinado para un pool existente solo HDD

  1. Obtén la línea base de la carga.
    Captura: zpool iostat -v 2, iostat -x 2, estadísticas ARC y procesos top de I/O. Guárdalo en algún lugar.
  2. Valida lo no negociable.
    Salud del pool, ashift, no discos fallando, no errores de lectura silenciosos. Si un disco está lento, arregla hardware primero.
  3. Separa datasets por carga.
    Como mínimo: tank/vm, tank/db, tank/backup, tank/home. El afinado es por dataset; diseña tu espacio de nombres en consecuencia.
  4. Establece propiedades de dataset seguras y con criterio.

    • La mayoría: compression=lz4, atime=off
    • Backup/medios: recordsize=1M
    • Bases de datos: empezar en recordsize=16K o 32K (probar)
    • VM zvols: fijar volblocksize correctamente al crear
  5. Maneja las escrituras sync con ingeniería, no negación.
    Mantén sync=standard. Si la latencia es inaceptable, la solución principal es batching en la aplicación o mover la ruta sync a otra capa (incluso si eso implica “más RAM y menos fsync”).
  6. Aplica headroom.
    Política interna: mantener pools HDD por debajo de ~80% CAP para rendimiento consistente. Si no puedes, estás en una sala de espera de incidentes de capacidad.
  7. Gestiona snapshots como si costaran dinero (porque lo hacen).
    La retención por churn de VM debe ser corta. Archivar snapshots debe ser deliberado y limitado.
  8. Programa scrubs y vigila su tendencia.
    Scrub mensual (común) fuera de pico. Rastrear duración. Investigar cambios.
  9. Vuelve a medir después de cada cambio.
    Una cosa a la vez. Compara con la línea base. Mantén lo que ayuda. Revierte lo que no.

Checklist: cuando un equipo pregunta “¿Podemos hacerlo más rápido sin SSDs?”

  • ¿La carga es mayormente secuencial? Si sí, ajusta recordsize y compresión y puedes ganar.
  • ¿Es I/O aleatorio en bloques pequeños? Si sí, mirrors y RAM ayudan; RAIDZ no va a volverse mágico.
  • ¿Es heavy en escrituras sync? Si sí, arregla semántica de la app o acepta latencia; no desactives sync a la ligera.
  • ¿El pool está >80% lleno o muy fragmentado? Si sí, la gestión de capacidad es trabajo de rendimiento.
  • ¿Algún disco está lento o con errores? Si sí, reemplázalo antes de tocar perillas.

Checklist: valores seguros por defecto para pools solo HDD (por dataset)

  • Compartidos generales: compression=lz4, atime=off, recordsize=128K
  • Backups: compression=lz4, recordsize=1M, sync=standard salvo aceptación explícita de riesgo
  • Bases de datos (archivos): empezar recordsize=16K o 32K, benchmark, mantener sync=standard
  • VMs: preferir vdevs mirror; para zvols fijar volblocksize correctamente al crear

Preguntas frecuentes

1) ¿Puedo obtener rendimiento “tipo SSD” en ZFS solo con HDD mediante afinado?

No. Puedes obtener rendimiento de “matriz HDD bien diseñada”. Eso sigue siendo valioso. La ganancia es eliminar I/O autoinfligido y alinear la carga con comportamiento secuencial.

2) ¿Debo usar RAIDZ o mirrors para pools solo HDD?

Mirrors para cargas sensibles a IOPS (VMs, bases de datos, metadata pesada). RAIDZ para almacenamiento de alta capacidad y I/O mayormente secuencial.
Si intentas ejecutar VMs en RAIDZ y se siente mal, es porque lo es.

3) ¿Es seguro compression=lz4 en producción?

Sí, y suele ser más rápido en pools HDD. Reduce I/O físico. La razón principal para desactivarlo es si la CPU es realmente el cuello de botella para ese dataset y la ratio de compresión está cerca de 1.00x.

4) ¿Qué recordsize debo usar?

Predeterminado 128K para propósito general. 1M para backups/medios/objetos grandes. Más pequeño (16K/32K) para bases de datos o cargas con I/O aleatorio pequeño,
pero prueba. Recordsize no es un knob de “máximo rendimiento”; es un knob de forma de I/O.

5) ¿Está bien poner sync=disabled para mejorar rendimiento?

Solo si puedes perder escrituras recientes y lo has aceptado explícitamente. También puede romper aplicaciones que dependen de fsync para corrección.
Para la mayoría de datos en producción: mantén sync=standard y arregla la carga.

6) ¿Necesito tunear parámetros del módulo ZFS para buen rendimiento?

Normalmente no. La mayoría de ganancias vienen del diseño, propiedades de dataset, dimensionamiento de ARC y prácticas operativas. Afinar kernel/módulo es trabajo de último tramo
y fácil de estropear, especialmente en actualizaciones.

7) ¿Por qué baja el rendimiento a medida que el pool se llena?

La presión del asignador y la fragmentación aumentan, el espacio libre es menos contiguo y ZFS tiene menos buenas opciones. Los HDDs pagan la penalidad de búsqueda.
Mantén margen; trátalo como parte del diseño.

8) ¿Con qué frecuencia debo scrubear un pool solo HDD?

Comúnmente mensual, programado fuera de pico. La respuesta real depende de tolerancia al riesgo, calidad de discos y ventanas de rebuild. Monitoriza duración de scrub y correcciones; las tendencias importan más que el calendario exacto.

9) ¿La deduplicación ayuda al rendimiento?

Rara vez en pools solo HDD. A menudo perjudica rendimiento y memoria. Si quieres rendimiento, usa compresión. Si quieres ahorro de espacio, mide y valida antes de habilitar dedup.

10) ¿Cuál es la mejora más grande sin SSDs?

Para cargas mayormente de lectura: más RAM (ARC). Para cargas mixtas: propiedades de dataset correctas y evitar tormentas de escrituras sync. Para VMs: mirrors.
Para “todo está lento”: deja de operar el pool cerca del full y reemplaza discos lentos.

Próximos pasos que puedes hacer esta semana

  1. Ejecuta el manual de diagnóstico rápido una vez en hora pico.
    Guarda salidas de zpool iostat, iostat -x y estadísticas ARC. Las líneas base son cómo dejas de adivinar.
  2. Separa datasets por carga y establece propiedades sensatas.
    Especialmente: recordsize para backups y atime=off para la mayoría de datasets de servidor.
  3. Caza tormentas de escrituras sync.
    Identifica quién hace fsync y por qué. Arréglalo en la capa de la aplicación si es posible. No “resuelvas” con sync=disabled.
  4. Busca un disco lento.
    Usa iostat -x y SMART. Un actor malo puede parecer una regresión sistémica.
  5. Aplica headroom y retención de snapshots.
    Si tu pool tiende por encima de 80% CAP, trátalo como incidente de capacidad. Es más barato que investigar un misterio de rendimiento.
  6. Registra la duración de scrub.
    Es aburrido. Funciona. Es la práctica que solo te burlas hasta que te salva.

El rendimiento de ZFS solo con HDD es una disciplina: buen diseño, semántica correcta, límites sensatos de dataset y medición implacable. No buscas ganar benchmarks.
Buscas ganar los martes.

← Anterior
Correo electrónico: phishing desde tu dominio — un manual de respuesta a incidentes que funciona
Siguiente →
MariaDB vs Percona Server: ajustes de rendimiento — qué es real y qué es marketing

Deja un comentario