Picos de latencia en ZFS: la lista de verificación que encuentra la causa

¿Te fue útil?

Los picos de latencia son el tipo de problema de almacenamiento que hace que gente inteligente diga cosas raras en los canales de incidentes. Todo está bien… hasta que no lo está. Tu API p99 se dispara durante 800 ms, tu base de datos empieza a “esperar IO” y tu servidor ZFS parece aburrido.

ZFS no es “lentamente aleatorio”. Normalmente está haciendo algo muy específico en una capa muy específica: escrituras sync, commit de TXG, cola en dispositivos, recuperación de memoria, scrub/resilver o un desajuste de carga poco afortunado. El truco es dejar de adivinar y ejecutar una lista de verificación que reduzca el cuello de botella en minutos, no horas.

Guía de diagnóstico rápido (primero/segundo/tercero)

Esta es la secuencia de “tengo cinco minutos antes de que mi jefe entre en la llamada”. No optimices nada todavía. No cambies propiedades como si fueras un DJ. Simplemente identifica la capa donde se está yendo el tiempo.

Primero: confirma que es latencia de almacenamiento, no programación de CPU o red

  1. Comprueba la carga del sistema y el tiempo de espera de IO. Si la CPU está saturada o hay thrashing de memoria, el almacenamiento parecerá culpable aun cuando no lo sea.

    cr0x@server:~$ uptime
     14:22:18 up 18 days,  3:11,  2 users,  load average: 7.81, 7.34, 6.92
    cr0x@server:~$ vmstat 1 5
    procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
     r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
     2  1      0  82120  15640 912340    0    0   120   980  540  890 12  5 73 10  0
     1  2      0  81288  15640 905112    0    0   240  2030  620 1010 10  6 62 22  0
    

    Qué significa: Un wa alto (IO wait) sugiere que la CPU suele estar detenida esperando IO. Un b alto significa procesos bloqueados, a menudo por disco.

    Decisión: Si los picos de wa coinciden con los picos de latencia de la aplicación, sigue con las comprobaciones específicas de ZFS. Si us/sy está al máximo, revisa primero los cuellos de botella de CPU (compresión, checksums, cifrado).

  2. Revisa la red si usas NFS/SMB/iSCSI.

    cr0x@server:~$ ss -ti | head -n 15
    State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
    ESTAB 0      0      10.0.0.12:2049    10.0.2.45:51712  timer:(keepalive,38min,0) ino:0 sk:3b2
    	 cubic wscale:7,7 rto:204 retrans:0/0 rtt:0.337/0.012 ato:40 mss:1448 pmtu:1500 rcvmss:1448 advmss:1448 cwnd:10 bytes_sent:818244 bytes_acked:818244 bytes_received:94412 segs_out:571 segs_in:558 send 343Mb/s lastsnd:12 lastrcv:12 lastack:12 pacing_rate 686Mb/s
    

    Qué significa: Retransmisiones y grandes oscilaciones de RTT pueden imitar “picos de almacenamiento”.

    Decisión: Si la red está limpia (RTT estable, sin retransmisiones), céntrate en disco/ZFS.

Segundo: identifica si el pico está relacionado con escrituras sync/TXG

  1. Observa la latencia de ZFS a nivel de pool.

    cr0x@server:~$ zpool iostat -v -l 1 10
                                  capacity     operations     bandwidth     total_wait     disk_wait
    pool                        alloc   free   read  write   read  write   read  write   read  write
    tank                        3.12T  1.45T    210   9800  12.3M  402M   2.1ms  85ms   1.9ms  82ms
      raidz2-0                  3.12T  1.45T    210   9800  12.3M  402M   2.1ms  85ms   1.9ms  82ms
        sda                         -      -     20   1250  1.3M  50.1M  1.8ms  88ms   1.7ms  84ms
        sdb                         -      -     22   1210  1.4M  49.6M  2.0ms  86ms   1.8ms  83ms
        sdc                         -      -     19   1220  1.2M  50.0M  2.1ms  84ms   1.9ms  81ms
    

    Qué significa: total_wait es lo que sienten los llamantes; disk_wait aísla el tiempo de servicio del dispositivo. Si total_wait es alto pero disk_wait es bajo, estás encolando por encima de los discos (TXG, throttling, contención).

    Decisión: Si la espera de escritura salta a decenas/centenas de ms durante los picos, sospecha escrituras sync, SLOG, presión de commit TXG o saturación del pool.

Tercero: busca “IO de mantenimiento” y contención obvia

  1. ¿Se está ejecutando un scrub/resilver?

    cr0x@server:~$ zpool status -v tank
      pool: tank
     state: ONLINE
    status: One or more devices is currently being scrubbed.
      scan: scrub in progress since Mon Dec 23 02:01:13 2025
            1.22T scanned at 1.02G/s, 438G issued at 365M/s, 3.12T total
            0B repaired, 14.04% done, 0:02:45 to go
    config:
    
            NAME        STATE     READ WRITE CKSUM
            tank        ONLINE       0     0     0
              raidz2-0  ONLINE       0     0     0
                sda     ONLINE       0     0     0
                sdb     ONLINE       0     0     0
                sdc     ONLINE       0     0     0
    

    Qué significa: Los scrubs y resilvers son tormentas legítimas de IO. También cambian los patrones de IO (más lecturas, más metadatos).

    Decisión: Si los picos coinciden con ventanas de scrub/resilver, reprograma, ajusta el comportamiento del scrub o provisiona suficiente margen para sobrevivir al mantenimiento.

Un modelo mental práctico de la latencia en ZFS

Los picos de latencia en ZFS rara vez provienen de un ajuste mágico. Provienen de una canalización donde cualquier etapa puede atascarse:

  • Semántica de la aplicación: escrituras sync vs async, tormentas de fsync, puntos de control de bases de datos.
  • Capa del sistema de archivos: propiedades del dataset (recordsize, compression, atime), comportamiento de metadatos y bloques pequeños, vdevs especiales.
  • ARC y memoria: los aciertos de caché son rápidos; los fallos de caché y la agitación por desalojo no lo son. La presión de memoria lo enfurece todo.
  • TXG (grupos de transacción): ZFS agrupa cambios y los confirma. Cuando el trabajo de commit se acumula, puedes ver pausas periódicas o olas de latencia de escritura.
  • ZIL/SLOG (escrituras sync): las escrituras sync se reconocen después de que se registran de forma segura. Si el dispositivo de log es lento, tu aplicación aprende palabras nuevas.
  • Topología de vdev y discos: la matemática de RAIDZ, profundidad de cola, rarezas de SMR, fallos de firmware, políticas de caché de escritura.
  • Capa de dispositivo de bloque: elección de scheduler, multipath, HBAs, timeouts de unidad.

Cuando alguien dice “picos en ZFS”, pregunta: ¿picos dónde? ¿En latencia de lectura? ¿de escritura? ¿solo en escrituras sync? ¿solo en metadatos? ¿solo cuando el pool está al 80%? ¿solo durante backups? Tu trabajo es convertir “con picos” en una gráfica con un culpable.

Una opinión que te ahorrará tiempo: trata ZFS como una base de datos. Tiene su propio batching (TXGs), logging (ZIL), caché (ARC) y trabajo en segundo plano (scrub/resilver). Si no afinarías una base de datos cambiando perillas al azar, no lo hagas con ZFS tampoco.

Idea parafraseada de Werner Vogels (CTO de Amazon): “Todo falla, todo el tiempo; diseña sistemas que lo esperen.” Eso incluye tu presupuesto de latencia de almacenamiento.

Broma #1: ZFS no tiene “cambios de humor”. Tiene “contabilidad de I/O”. Es menos divertido, pero más accionable.

Hechos interesantes y breve historia (por qué ZFS se comporta así)

  • ZFS debutó a mediados de los 2000 con checksum de extremo a extremo como característica de primera clase, no como un añadido. Esa decisión influye en la latencia porque cada bloque leído puede implicar verificación de checksum.
  • El ZIL existe incluso sin un SLOG dedicado. Si no añades un dispositivo de log, ZFS usa espacio en los dispositivos del pool. Tu configuración “sin SLOG” todavía tiene logging sync; solo es más lenta y compite con las escrituras normales.
  • El commit de TXG es periódico. ZFS agrupa datos sucios y metadatos, luego los vacía. Ese agrupamiento aumenta el rendimiento pero puede crear pulsos rítmicos de latencia bajo carga sostenida de escritura.
  • Copy-on-write es a la vez una ventaja y un impuesto. Evita sobrescrituras en su lugar (bueno para integridad y snapshots) pero puede aumentar la fragmentación y el trabajo de metadatos con el tiempo, afectando la latencia final.
  • RAIDZ no es “RAID gratis”. Ahorra discos pero hace que las escrituras pequeñas y aleatorias sean caras (patrones read-modify-write). La latencia en cola sufre primero.
  • La realidad de 4K llegó después de muchos arrays. Elecciones de ashift mal alineadas (o suposiciones antiguas de 512e) pueden convertir “escrituras normales” en IO amplificado.
  • ARC fue diseñado para ser adaptable, no discreto. Consumirá memoria para caché; si tu carga necesita RAM en otro lugar (VMs, page cache, bases de datos), la presión resultante puede manifestarse como jitter de almacenamiento.
  • Los vdevs especiales son la historia moderna de “SSD para metadatos” en ZFS. Pueden reducir dramáticamente la latencia de metadatos—salvo que se saturen o fallen, en cuyo caso cambia toda la experiencia del pool.
  • La compresión se volvió popular en ZFS antes de ser cool. Intercambia ciclos de CPU por menos IO; en NVMe rápidos, la CPU se vuelve cuello de botella más a menudo de lo que la gente espera, causando síntomas de “latencia de almacenamiento”.

Listas de verificación / plan paso a paso (con comandos)

Este es el evento principal: tareas prácticas que puedes ejecutar durante un incidente y otra vez en un postmortem en calma. Cada tarea incluye (1) comando, (2) qué significa la salida, (3) la decisión que tomas.

Tarea 1: Capturar la latencia a nivel de pool y separar encolamiento del tiempo de servicio del disco

cr0x@server:~$ zpool iostat -l 1 30
                              capacity     operations     bandwidth     total_wait     disk_wait
pool                        alloc   free   read  write   read  write   read  write   read  write
tank                        3.12T  1.45T    320   9200  18.2M  380M   3.2ms  96ms   2.8ms  91ms
tank                        3.12T  1.45T    310   9300  17.8M  387M   2.9ms  140ms  2.6ms  132ms
tank                        3.12T  1.45T    290   9100  16.4M  376M   3.0ms  35ms   2.7ms  31ms

Qué significa: Cuando total_wait se infla, es visible por el usuario. Si disk_wait lo acompaña, los discos (o HBAs) son lentos. Si disk_wait se mantiene modesto mientras total_wait salta, estás limitado por encima del disco: throttling, congestión de TXG, contención del ZIL, contención de locks.

Decisión: Los picos impulsados por disk_wait te llevan a comprobaciones de dispositivo/hardware/cola. Los picos solo en total_wait te llevan a comprobaciones de sync/TXG/ARC.

Tarea 2: Encontrar el vdev culpable o el disco lento

cr0x@server:~$ zpool iostat -v -l 2 10 tank
                              capacity     operations     bandwidth     total_wait     disk_wait
pool                        alloc   free   read  write   read  write   read  write   read  write
tank                        3.12T  1.45T    300   9100  16.8M  375M   3.0ms  72ms   2.7ms  68ms
  raidz2-0                  3.12T  1.45T    300   9100  16.8M  375M   3.0ms  72ms   2.7ms  68ms
    sda                         -      -     30   1200  1.8M  48.9M  2.6ms  70ms   2.4ms  66ms
    sdb                         -      -     29   1180  1.7M  48.2M  2.7ms  72ms   2.5ms  69ms
    sdc                         -      -     28   1190  1.6M  48.6M  35ms   250ms  33ms   240ms

Qué significa: Un dispositivo con 10× latencia arrastra un vdev RAIDZ porque la matemática de paridad obliga a coordinar. Los mirrors también sufren: las lecturas pueden evitar un lado lento, las escrituras no.

Decisión: Si un disco es el outlier, comprueba SMART, cableado, pathing del HBA. Reemplaza el disco si sigue spikkeando. No “ajustes ZFS” alrededor de un disco moribundo.

Tarea 3: Confirma si las escrituras son síncronas (y si tu carga las fuerza)

cr0x@server:~$ zfs get -r sync tank
NAME        PROPERTY  VALUE  SOURCE
tank        sync      standard  default
tank/db     sync      standard  inherited from tank
tank/vm     sync      always    local

Qué significa: sync=always fuerza que cada escritura se comporte como sync. Algunas cargas lo necesitan (bases de datos), muchas no (logs masivos). standard obedece a la aplicación (fsync/O_DSYNC).

Decisión: Si un dataset tiene sync=always sin razón clara, cámbialo a standard tras confirmar las necesidades de durabilidad de la aplicación. Si realmente lo requiere, debes proporcionar un buen SLOG o aceptar la latencia.

Tarea 4: Verifica si tienes un SLOG y si está sano

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

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          raidz2-0  ONLINE       0     0     0
            sda     ONLINE       0     0     0
            sdb     ONLINE       0     0     0
            sdc     ONLINE       0     0     0
        logs
          nvme0n1p2 ONLINE       0     0     0

Qué significa: Si el pool tiene una sección logs, tienes un dispositivo de log separado. Si no, las escrituras sync aterrizan en los vdevs principales. También: un SLOG ONLINE puede seguir siendo lento.

Decisión: Si la latencia sync te está matando y no hay SLOG, añade uno (reflejado si la durabilidad importa). Si hay SLOG, benchmarkea/valídalo; NVMe consumidor barato puede tener latencias extremas en cola bajo semánticas PLP.

Tarea 5: Valida recordsize y volblocksize del dataset frente a la carga

cr0x@server:~$ zfs get recordsize,volblocksize,primarycache,logbias tank/vm tank/db
NAME      PROPERTY      VALUE   SOURCE
tank/vm   recordsize    128K    inherited from tank
tank/vm   volblocksize  -       -
tank/vm   primarycache  all     default
tank/vm   logbias       latency default
tank/db   recordsize    16K     local
tank/db   volblocksize  -       -
tank/db   primarycache  all     default
tank/db   logbias       latency local

Qué significa: Las bases de datos suelen preferir recordsize de 8K–16K para ficheros; las imágenes de VM pueden preferir volblocksize ajustado al crear el zvol (valores comunes 8K–64K según el hipervisor). Los tamaños erróneos aumentan read-modify-write y la presión de metadatos.

Decisión: Si ves un desajuste, planifica una migración (no puedes cambiar volblocksize después de la creación). No hagas esto a mitad de incidente salvo que te guste las horas extra.

Tarea 6: Comprueba el llenado del pool y la presión por fragmentación

cr0x@server:~$ zpool list -o name,size,alloc,free,cap,frag,health
NAME  SIZE  ALLOC  FREE  CAP  FRAG  HEALTH
tank  4.50T 3.12T 1.38T  69%   41%  ONLINE

Qué significa: Un cap alto (especialmente por encima de ~80–85%) y una fragmentación elevada suelen correlacionar con peor latencia final, particularmente en HDD RAIDZ. ZFS tiene menos segmentos libres contiguos; la asignación se vuelve más costosa.

Decisión: Si cap es alto, libera espacio (borra, mueve, amplía). Si frag está alta y el rendimiento colapsa, considera reescribir datos (send/recv a un pool nuevo) o añadir vdevs para aumentar las opciones de asignación.

Tarea 7: Comprueba tamaño del ARC, ratio de aciertos y señales de presión de memoria

cr0x@server:~$ arcstat 1 5
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
14:25:01   820    44      5    10    1    34    4     0    0   62G   64G
14:25:02   910   210     23    80    9   130   14     0    0   62G   64G
14:25:03   840   190     22    70    8   120   14     0    0   58G   64G
14:25:04   870    55      6    12    1    43    5     0    0   58G   64G

Qué significa: Saltos en miss% y contracciones del ARC pueden indicar presión de memoria o un cambio de carga. Cuando ARC no puede mantener datos calientes, las lecturas se convierten en IO real de disco y la latencia se vuelve errática.

Decisión: Si ARC es volátil y miss% se dispara durante incidentes, revisa la memoria global, comportamiento de reclaim y si algo más (VMs, contenedores) está usando RAM. Considera reservar memoria, ajustar arc_max o mover el tenant ruidoso.

Tarea 8: Identifica presión de escrituras sync vía estadísticas del ZIL

cr0x@server:~$ kstat -p | egrep 'zfs:0:zil:|zfs:0:vdev_sync' | head
zfs:0:zil:zil_commit_count                        184220
zfs:0:zil:zil_commit_writer_count                  12011
zfs:0:zil:zil_commit_waiter_count                  31255
zfs:0:vdev_sync:vdev_sync_write_bytes              98342199296

Qué significa: Contadores crecientes de commits y waiters apuntan a actividad sync intensa. Si los waiters se amontonan, las aplicaciones están bloqueando en commits de log.

Decisión: Si la presión sync es alta, valídalo con la latencia del SLOG, ajustes de logbias y comprueba si la app está haciendo fsyncs excesivos (o funcionando en modo “seguro pero lento”).

Tarea 9: Confirma si un scrub, resilver o recuperación de errores de dispositivo consume IO

cr0x@server:~$ zpool status tank | sed -n '1,25p'
  pool: tank
 state: ONLINE
  scan: scrub repaired 0B in 0 days 03:22:11 with 0 errors on Sun Dec 22 03:22:11 2025
config:

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          raidz2-0  ONLINE       0     0     0
            sda     ONLINE       0     0     0
            sdb     ONLINE       0     0     0
            sdc     ONLINE       0     0     0

Qué significa: Un estado de pool limpio no significa “sin IO en segundo plano”, pero descarta tareas de mantenimiento obvias.

Decisión: Si hay un scan activo durante los picos, decide si pausarlo (en algunos entornos), reprogramarlo o aceptar el impacto como coste de integridad.

Tarea 10: Comprueba el encolamiento y la latencia por disco en la capa de bloque de Linux

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

Device            r/s     w/s   rMB/s   wMB/s avgrq-sz avgqu-sz await r_await w_await  svctm  %util
sda              25.0  1200.0     1.6    49.0    86.0     9.2  62.0   18.0   63.0   0.8   98.0
sdb              24.0  1180.0     1.5    48.3    86.0     8.9  60.0   17.5   61.0   0.8   97.0
sdc              23.0  1190.0     1.4    48.6    86.0    28.1  220.0  21.0  225.0   0.8   99.0

Qué significa: avgqu-sz y await muestran la acumulación de la cola. %util cerca de 100% indica saturación. Un solo disco con cola y await enormes es el generador de picos de latencia.

Decisión: Si la capa de bloque muestra el mismo disco outlier que ZFS, esto no es un problema de tuning de ZFS. Extrae SMART, comprueba los logs del controlador y prepara el reemplazo.

Tarea 11: Comprueba la salud del disco y contadores que indican “este disco te está mintiendo”

cr0x@server:~$ smartctl -a /dev/sdc | egrep -i 'Reallocated|Pending|Uncorrect|CRC|Power_On_Hours|Temperature|Error'
Power_On_Hours          38122
Temperature_Celsius     44
Reallocated_Sector_Ct   0
Current_Pending_Sector  8
Offline_Uncorrectable   2
UDMA_CRC_Error_Count    19

Qué significa: Sectores pendientes/uncorrectable pueden causar reintentos internos largos. Errores CRC suelen indicar problemas de cableado/backplane, que producen picos intermitentes de latencia en lugar de fallos claros.

Decisión: Sectores pendientes y errores no corregibles son señal de “reemplazar pronto”; los errores CRC son señal de “arregla la ruta”. Cualquiera puede explicar stalls periódicos de 200 ms–2 s.

Tarea 12: Comprueba propiedades del dataset que silenciosamente causan IO extra

cr0x@server:~$ zfs get -r atime,compression,xattr,acltype,logbias tank | head -n 20
NAME      PROPERTY     VALUE     SOURCE
tank      atime        off       local
tank      compression  lz4       local
tank      xattr        sa        local
tank      acltype      posixacl  local
tank      logbias      latency   default
tank/db   atime        off       inherited from tank
tank/db   compression  lz4       inherited from tank
tank/db   xattr        sa        inherited from tank
tank/db   acltype      posixacl  inherited from tank
tank/db   logbias      latency   local

Qué significa: atime=on en cargas de lectura intensiva genera escrituras en lecturas, lo cual es una forma especial de sabotaje. La compresión puede reducir IO pero aumentar latencia de CPU. xattr=sa suele ayudar en cargas con muchos metadatos.

Decisión: Si ves atime=on en datasets calientes y no lo necesitas, apágalo. Si la compresión es intensa y los picos de CPU correlacionan con picos de latencia, considera ajustar la compresión o añadir CPU.

Tarea 13: Comprueba si snapshots y borrados están creando trabajo patológico de metadatos

cr0x@server:~$ zfs list -t snapshot -o name,used,creation -s creation | tail -n 5
tank/db@auto-2025-12-25-0100   2.1G  Mon Dec 25 01:00 2025
tank/db@auto-2025-12-25-0200   2.2G  Mon Dec 25 02:00 2025
tank/db@auto-2025-12-25-0300   2.2G  Mon Dec 25 03:00 2025
tank/db@auto-2025-12-25-0400   2.3G  Mon Dec 25 04:00 2025
tank/db@auto-2025-12-25-0500   2.4G  Mon Dec 25 05:00 2025

Qué significa: Los snapshots son baratos de crear, no siempre baratos de mantener. Grandes árboles de snapshots + sobrescrituras constantes pueden aumentar la fragmentación y la agitación de metadatos, lo que se manifiesta como jitter.

Decisión: Si tienes políticas agresivas de snapshots en datasets de alta rotación, ajusta la retención o mueve esa carga a mirrors/NVMe donde la agitación de metadatos sea menos castigadora.

Tarea 14: Valida ashift y alineación de sectores (la configuración “para siempre”)

cr0x@server:~$ zdb -C tank | egrep 'ashift|path' | head -n 20
            path: '/dev/disk/by-id/ata-ST12000NM0008-2H2161_ZHZ12345'
            ashift: 12
            path: '/dev/disk/by-id/ata-ST12000NM0008-2H2161_ZHZ23456'
            ashift: 12

Qué significa: ashift=12 significa sectores de 4K; ashift=9 significa 512B. Fijar ashift demasiado bajo en discos 4K puede crear amplificación de escrituras y latencias finales feas.

Decisión: Si ashift es incorrecto, no puedes “arreglarlo” in situ. Planifica una reconstrucción/migración. Ponlo en el postmortem como “no lo volveremos a hacer”.

Tarea 15: Encuentra señales de throttling y presión de TXG (Linux OpenZFS)

cr0x@server:~$ cat /proc/spl/kstat/zfs/txgs
txg                            birth                    state                    ndirty
1064217                        1703510152               open                     2147483648
1064216                        1703510150               quiescing                1987654321
1064215                        1703510148               syncing                  1876543210

Qué significa: Múltiples TXGs en quiescing/syncing con muchos bytes sucios sugiere que el sistema lucha por vaciar. Eso puede manifestarse como picos de latencia de escritura y bloqueos ocasionales cuando ZFS aplica backpressure.

Decisión: Si los TXGs están atascados sincronizando, reduce la tasa de dirtying (throttle de la app, ajustar ráfagas de escritura), mejora el rendimiento de vdev, o reduce tareas de fondo que compitan. No aumentes los límites de datos sucios y esperes lo mejor.

Tarea 16: Comprueba la salud y utilización de vdevs especiales (metadatos en SSD)

cr0x@server:~$ zpool status -v 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
            sda        ONLINE       0     0     0
            sdb        ONLINE       0     0     0
            sdc        ONLINE       0     0     0
        special
          mirror-1     ONLINE       0     0     0
            nvme1n1    ONLINE       0     0     0
            nvme2n1    ONLINE       0     0     0

Qué significa: Un vdev especial contiene metadatos (y opcionalmente bloques pequeños). Si está poco saludable o es pequeño, las operaciones de metadatos pueden explotar incluso si los datos a granel están bien. También: si el vdev especial falla y pierdes redundancia, el riesgo del pool se dispara.

Decisión: Si hay un vdev especial, trátalo como infraestructura de nivel 0. Monitorízalo como monitorizas el dispositivo WAL de tu base de datos. Si es pequeño o lento, arréglalo antes de perseguir fantasmas.

Tarea 17: Correlaciona el comportamiento sync de la aplicación con los picos de almacenamiento

cr0x@server:~$ pidstat -d 1 5 | egrep 'postgres|mysqld|qemu|java' || true
14:28:11      UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
14:28:12      113      2218      0.00  402112.00  1024.00     312  postgres
14:28:13      113      2218      0.00  398740.00   980.00     411  postgres

Qué significa: El crecimiento de iodelay indica que el proceso espera IO. Si tu proceso de BD es el que está bloqueado durante los picos, deja de culpar al balanceador de carga.

Decisión: Si un proceso domina la espera de IO, analiza su patrón de escritura (checkpoints, tormentas de fsync, trabajos de backup). La solución puede estar en el horario de la aplicación, no en ZFS.

Tres microhistorias corporativas desde el frente

Microhistoria 1: El incidente causado por una suposición errónea

Migraron un servicio transaccional a un nuevo clúster de almacenamiento con ZFS. La revisión de arquitectura fue fluida. “Ahora estamos en SSDs”, dijo alguien, lo que se trató como una solución universal para el riesgo de rendimiento.

La primera semana fue tranquila. Luego llegó fin de mes. El servicio empezó a mostrar stalls de escritura de 1–3 segundos. No constantes—lo justo para provocar reintentos, amplificar la carga y hacer que el resto del sistema pareciera inestable. El comandante del incidente hizo el recorrido habitual: CPU bien, red bien, “ZFS está spikando”.

La suposición errónea fue sutil: asumieron que la presencia de un dispositivo NVMe significaba “escrituras sync rápidas”. El pool no tenía SLOG dedicado y la disposición de vdev era RAIDZ. La carga hacía fsyncs frecuentes. Durante ráfagas, el pool tuvo que hacer logging sync en los mismos dispositivos que manejaban escrituras normales y trabajo de paridad.

Cuando miraron total_wait versus disk_wait, fue obvio: disk_wait se disparaba durante las ráfagas sync. Añadieron un SLOG espejado con protección contra pérdida de energía y movieron solo el dataset intensivo en sync a él (manteniendo otros datasets estándar). Los picos de latencia no desaparecieron; se redujeron hasta el ruido de fondo donde las gráficas de monitorización van a morir.

La lección que quedó: “SSD” no es una garantía de rendimiento. Es un medio. La arquitectura sigue siendo tu problema.

Microhistoria 2: La optimización que salió mal

Otro equipo luchaba contra picos de latencia aleatorios de lectura en un servidor de archivos ocupado. Alguien sugirió desactivar la compresión “para reducir la CPU”. Sonaba razonable: menos CPU, más velocidad. Cambiaron compression=off en el dataset más caliente.

En pocas horas, el p99 empeoró. No ligeramente—significativamente peor. La CPU bajó un poco, sí, pero el ancho de banda de lectura de disco subió, y también la profundidad de cola. El pool ahora servía más IO físico para la misma carga lógica, y esos IOs extra son los que aparecen en la latencia final.

El problema real resultó ser misses de ARC provocados por una nueva mezcla de cargas más un límite de memoria impuesto por la configuración de contenedores. La compresión había estado ocultando parte de la presión al hacer los bloques más pequeños, incrementando efectivamente la “capacidad útil” del ARC y reduciendo el tráfico de disco.

Revirtieron la compresión a lz4, corrigieron la política de memoria de los contenedores y establecieron expectativas: “Optimizamos para p99, no por métricas de vanidad de CPU”. La compresión no era la villana; la presión de memoria no contabilizada sí lo era.

Broma #2: Desactivar la compresión para reducir latencia es como quitarte el cinturón de seguridad para mejorar la aceleración—técnicamente menos restricciones, prácticamente un mal día.

Microhistoria 3: La práctica aburrida pero correcta que salvó el día

Una gran empresa ejecutaba cargas mixtas: VMs, comparticiones de archivos y algunas bases de datos que todos fingían que no eran “críticas” hasta que lo eran. Tenían una política: ventanas semanales de scrub, comprobaciones SMART mensuales e investigación inmediata de cualquier CRC creciente. No era trabajo glamuroso. También fue la razón por la que su peor incidente nunca ocurrió.

Un jueves, la latencia empezó a spikear en ráfagas cortas: 200 ms, luego normal, luego 500 ms, luego normal. Las gráficas eran enloquecedoras. Las estadísticas de ZFS no gritaban “pool muriendo”. Ningún disco había fallado oficialmente. Sin embargo, los usuarios notaban.

El on-call ejecutó zpool iostat -v -l y vio un disco ocasionalmente saltando a un enorme disk_wait. SMART mostró un UDMA_CRC_Error_Count en aumento. Eso no es “reemplaza disco”, eso es “arregla la ruta”. El equipo volvió a asentar la unidad, cambió el cable SAS y siguió con su trabajo.

Una semana después, apareció un spike similar en otro chasis. Mismo guion, mismo arreglo, sin drama. La práctica aburrida—tratar los errores CRC como alertas de primera clase—previno un resilver en cámara lenta que habría aplastado el rendimiento por días.

La lección: el mejor pico de latencia es el que nunca tienes porque creíste en tu telemetría.

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

1) “Cada 5–10 segundos, las escrituras se detienen”

Síntoma: olas rítmicas de latencia de escritura; las aplicaciones hacen time out en commits; las gráficas parecen un latido.

Causa raíz: presión de commit TXG. Los datos sucios se acumulan más rápido de lo que el pool puede vaciar, así que ZFS aplica backpressure y lo sientes como stalls periódicos.

Solución: Reduce fuentes de escritura en ráfaga (trabajos por lotes, checkpoints), añade rendimiento de vdev, evita RAIDZ para cargas intensivas de escrituras aleatorias y mantén scrub/resilver fuera de horas pico.

2) “Solo las escrituras sync son lentas; async está bien”

Síntoma: lecturas bien; escrituras bufferizadas bien; apps con fsync sufren; NFS con export sync se siente terrible.

Causa raíz: ruta ZIL lenta—o no hay SLOG, o el SLOG tiene latencia en cola terrible, o una configuración forzada (sync=always).

Solución: Añade un SLOG espejado adecuado (con protección contra pérdida de energía), establece sync=standard salvo que realmente necesites always, y valida que la app no esté fsyncando en exceso.

3) “El p99 de lectura aleatoria empeoró tras añadir más tenants”

Síntoma: la media está bien; p99 no; los picos correlacionan con otros trabajos.

Causa raíz: agitación de expulsión del ARC y misses de caché por contención de memoria (contenedores/VMs), además de competencia de IO en los mismos vdevs.

Solución: Reserva RAM, fija límites sensatos de ARC, aísla cargas ruidosas en pools/vdevs separados, o añade medios más rápidos para metadatos/IO pequeño.

4) “Los picos aparecieron después de habilitar cifrado/compresión”

Síntoma: CPU salta durante los picos; discos no están totalmente utilizados.

Causa raíz: pipeline limitado por CPU: compresión, checksums o cifrado empujan trabajo a la CPU. Si la programación de CPU falla, la finalización de IO parece “lenta”.

Solución: Perfil de CPU, fija cargas adecuadamente, mejora CPU o ajusta nivel de compresión. No desactives funciones de integridad a ciegas; demuestra primero que la CPU es el cuello de botella.

5) “Todo está bien hasta que empieza scrub/resilver, entonces los usuarios gritan”

Síntoma: caídas previsibles de rendimiento durante mantenimiento.

Causa raíz: falta de margen. IO de mantenimiento compite con IO de producción; en HDD RAIDZ, la penalización es especialmente dura.

Solución: Programa scrubs, ajusta prioridades si tu plataforma lo permite y—esto no es popular—compra suficientes discos para tener margen.

6) “Una VM hace que el almacenamiento de todos spikee”

Síntoma: comportamiento de vecino ruidoso; ráfagas de escrituras sync o IO aleatorio pequeño dominan.

Causa raíz: carga forzada sync (journaling, bases de datos) compartiendo pool con lecturas sensibles a latencia; o un volblocksize de zvol mal ajustado que causa amplificación de escritura.

Solución: Aísla el almacenamiento de esa VM, ajusta zvols correctamente, proporciona SLOG si se requiere sync, o aplica QoS por carga en una capa superior si la tienes.

7) “Los picos desaparecen tras un reboot… luego vuelven”

Síntoma: el reinicio cura los síntomas temporalmente.

Causa raíz: la caché ARC caliente oculta problemas de disco hasta que vuelven los misses; o la fragmentación a largo plazo / agitación de snapshots retorna; o los reintentos de hardware se acumulan otra vez.

Solución: Usa el período caliente para recoger métricas base y luego persigue la verdadera causa raíz: salud del disco, cambios de carga, dimensionado de caché, gestión de fragmentación.

Una lista de verificación más estricta: aísla la capa del cuello de botella

Paso 1: Clasifica el pico por tipo de IO

  • Picos de lectura: busca misses de ARC, latencia de metadatos, un disco lento o saturación del vdev especial.
  • Picos de escritura: determina sync vs async. Para async: TXG, saturación de vdev, matemática RAIDZ. Para sync: ruta ZIL/SLOG.
  • Picos de metadatos: muchas creaciones/borrados de archivos, snapshots, ficheros pequeños, operaciones de directorio. Los vdevs especiales ayudan; HDD RAIDZ lo odia.

Paso 2: Decide si estás saturado o con jitter

  • Saturado: %util cerca de 100%, colas largas, disk_wait alto. La solución es capacidad/rendimiento: más vdevs, medios más rápidos, menos trabajos competitivos.
  • Con jitter: la utilización media es moderada pero el p99 es terrible. A menudo retries de firmware, CRC/cableado, comportamiento SMR, GC en SSD/NVMe de consumo o latencia en el log.

Paso 3: Verifica que la topología encaje con la carga

  • Escrituras aleatorias intensas y cargas sync prefieren mirrors (o mirrors rayados) para latencia.
  • RAIDZ puede ser excelente para throughput secuencial y eficiencia de capacidad, pero no es especialista en escrituras aleatorias de baja latencia.
  • Los vdevs especiales pueden transformar el rendimiento de metadatos, pero ahora forman parte de la historia de supervivencia del pool. Reflejalos.

Paso 4: Haz cambios reversibles primero

  • Reprograma scrubs y backups.
  • Ajusta propiedades de dataset como atime, logbias y primarycache cuando esté justificado.
  • Sólo tras tener evidencia: añade SLOG, añade vdevs, migra cargas, reconstruye el pool para el ashift correcto.

Patrones de picos de latencia y lo que suelen significar

Patrón: “Picos cortos, un disco siempre es el peor”

Este es el más fácil y el más comúnmente ignorado. Si un disco consistentemente muestra 10× latencia, el pool está haciendo trabajo en grupo con un compañero que fuma durante los simulacros de incendio. Cámbialo o arregla la ruta.

Patrón: “Picos solo durante eventos intensivos en fsync”

Historia clásica de ZIL/SLOG. También aparece con NFS de semántica síncrona, tormentas de commits de bases de datos o sistemas de archivos de invitados VM con barreras agresivas. Si necesitas durabilidad, necesitas hardware de logging durable y de baja latencia.

Patrón: “Picos cuando la memoria está justa”

Contracción del ARC + misses aumentan el IO físico. Si ejecutas ZFS en una máquina que también corre un zoológico de contenedores, sé explícito con presupuestos de memoria. “Estará bien” no es una estrategia de memoria.

Patrón: “Picos después de que creció la retención de snapshots”

Las cadenas largas de snapshots no son malas per se, pero pueden amplificar el coste de los borrados/sobrescrituras e incrementar la fragmentación. La latencia sufre primero; el throughput se ve bien hasta que no.

Patrón: “Picos tras añadir un vdev especial”

El vdev especial ayuda cuando es rápido y no está saturado. Si es pequeño, se convierte en punto caliente. Si no está espejado (no lo hagas), se convierte en el punto único de fallo del pool.

Preguntas frecuentes

1) ¿Son los picos de latencia de ZFS “normales” por los commits TXG?

Algo de periodicidad puede ser normal bajo carga sostenida de escritura, pero grandes picos p99 no son una característica. Si los TXGs causan paros visibles para el usuario, estás sobrecargando el pool o luchando contra un cuello de botella de sync/log.

2) ¿Debería poner sync=disabled para arreglar la latencia?

Sólo si te sientes cómodo reconociendo escrituras que pueden desaparecer ante una pérdida de energía o crash. Puede “arreglar” la latencia al eliminar la durabilidad. En sistemas regulados o transaccionales, eso no es una solución; es una decisión de carrera.

3) ¿Siempre necesito un SLOG?

No. Si tu carga raramente emite escrituras sync (o toleras la latencia), puedes vivir sin él. Si ejecutas bases de datos, almacenamiento de VM o NFS síncrono y te importa el p99, un buen SLOG suele marcar la diferencia entre calma y caos.

4) ¿Por qué un disco lento afecta tanto al pool?

Porque los vdevs hacen IO coordinado. En RAIDZ, la coordinación de paridad hace que el miembro más lento marque el ritmo. En mirrors, las escrituras aún van a ambos lados. La latencia final la domina el peor participante.

5) ¿Cuánto lleno es “demasiado lleno” para un pool ZFS?

Depende de la carga y el tipo de vdev, pero por encima de ~80–85% deberías esperar efectos de asignación y fragmentación—especialmente en HDD RAIDZ. Si la latencia es un objetivo, deja margen como si lo dijeras en serio.

6) ¿La compresión lz4 es buena o mala para la latencia?

Frecuentemente buena. Reduce IO físico y puede mejorar p99 si estabas limitado por IO. Puede ser mala si te vuelves CPU-bound (máquinas ocupadas, cifrado, núcleos débiles). Mide CPU e IO antes de decidir.

7) ¿Pueden los snapshots causar picos de latencia?

Indirectamente, sí. Snapshots frecuentes más alta rotación pueden aumentar la fragmentación y el trabajo de metadatos. Borrar grandes conjuntos de snapshots también puede crear actividad de fondo pesada que los usuarios notan.

8) ¿Los vdevs especiales siempre mejoran la latencia?

Mejoran metadatos y IO de bloque pequeño cuando están bien dimensionados y espejados. Pero añaden una nueva clase de cuello de botella: la saturación del vdev especial. Trátalos como nivel-0 y monitorízalos en consecuencia.

9) ¿RAIDZ siempre es peor que mirrors para latencia?

Para cargas con muchas escrituras aleatorias y que requieren baja latencia, los mirrors suelen ganar. RAIDZ puede ser excelente para cargas secuenciales y eficiencia de capacidad. Elige según el patrón de IO, no por ideología.

10) ¿Por qué los picos de latencia a veces parecen problemas de red?

Porque la aplicación experimenta “tiempo de espera” y no puede distinguir si es disco, programación de CPU, contención de locks o pérdida de paquetes. Por eso empiezas separando capas: vmstat/iostat/zpool iostat y luego red.

Conclusión: siguientes pasos que realmente reducen los picos

Si quieres menos picos de latencia en ZFS, no empieces por tunear. Empieza por clasificar y reunir evidencias.

  1. Instrumenta y captura: mantén una captura corta y rodante de zpool iostat -l, iostat -x y estadísticas de memoria durante las horas pico. Los picos que no se capturan se convierten en folklore.
  2. Arregla la jitter de hardware obvia: discos outlier, errores CRC y paths inestables. Reemplaza o repara primero; ajusta después.
  3. Respeta las semánticas sync: si la carga necesita sync, proporciona un SLOG real y valida su latencia en cola. Si no lo necesita, no forces sync “por si acaso”.
  4. Mantén margen: margen de capacidad y margen de rendimiento. Scrubs, resilvers y backups no son opcionales; planifícalos.
  5. Alinea la topología con la carga: mirrors para escrituras aleatorias sensibles a latencia, RAIDZ para capacidad/throughput donde corresponda, vdevs especiales para metadatos si puedes gestionarlos responsablemente.
  6. Haz un cambio a la vez: y mide. Si no puedes decir si ayudó, no ayudó—al menos no de forma fiable.

El objetivo de la lista de verificación no es convertirte en “experto en ZFS”. Es hacerte rápido encontrando la única cosa que está causando el pico, antes de que el canal de incidentes desarrolle su propio sistema meteorológico.

← Anterior
Velocidad de restauración: MariaDB vs PostgreSQL — cómo lograr RTO inferior a 15 minutos
Siguiente →
Debian 13 “Could not resolve host”: DNS/proxy/IPv6 — la ruta de diagnóstico más rápida

Deja un comentario