Problema

En entornos de virtualización con Proxmox es frecuente mezclar pools ZFS de diferentes tecnologías (SSD para arranque y VM, HDD para backup). Cuando una de esas pools recibe una carga de escritura intensa, el host y todas las VMs pueden quedar sin respuesta. El síntoma típico es un “I/O Pressure stall” que se aproxima al 100 % y que persiste hasta que la operación de disco finaliza o se aborta. El efecto colateral es la pérdida de conectividad (por ejemplo, un pfSense que deja de enrutar) y la aparente congelación del hipervisor.

Este comportamiento no está limitado a un modelo de hardware concreto; se reproduce en servidores con CPUs de alto número de hilos, grandes cantidades de RAM y configuraciones de ZFS que incluyen discos externos (USB‑UAS) o SSD de consumo. El problema se vuelve crítico cuando la carga de I/O proviene de distintas pools simultáneamente, porque el subsistema de ZFS compite por recursos comunes (memoria ARC, hilos de ZVOL, scheduler del kernel).

Causa

  1. Presión del ARC
    ZFS usa la RAM como caché de lectura/escritura (ARC). Cuando la carga de escritura supera la capacidad de la caché, el ARC se vuelve “agresivo”, expulsando datos y generando thrashing. En servidores con 64 GB de RAM, un ARC de 16 GB puede ser suficiente, pero si la carga de escritura es sostenida y los discos son lentos (HDD USB), el ARC consume la mayor parte de la RAM y deja poco espacio para el resto del sistema, provocando stalls.

  2. Límites de hilos de ZVOL
    Cada zvol (disco virtual) utiliza un pool de hilos configurable mediante zvol_threads. Si el número de hilos es insuficiente para la concurrencia de VMs, las peticiones de I/O se encolan y el kernel marca presión de I/O. El valor por defecto suele ser 8 hilos por zvol; en máquinas con 32 hilos de CPU ese número puede ser demasiado bajo.

  3. Sin SLOG dedicado
    Los discos SSD de consumo usados como pool de arranque carecen de una capa de ZIL (SLOG) optimizada. Cuando una VM escribe de forma síncrona, ZFS espera a que el dato llegue al disco. En un SSD de baja resistencia o en un disco externo USB, la latencia de la ZIL se dispara y bloquea el flujo de I/O.

  4. Scheduler y cola de I/O del kernel
    El scheduler predeterminado (mq-deadline o bfq) puede no ser el más adecuado para mezclas de SSD y HDD. Además, la cola de I/O del bloque (/sys/block/<dev>/queue/nr_requests) está limitada y se llena rápidamente bajo carga pesada, lo que genera stalls visibles en iostat.

  5. Contención entre pools
    Aunque los pools están físicamente separados, comparten el mismo bus PCIe/USB y la misma CPU. Un pico de I/O en la pool HDD (por ejemplo, una copia masiva) puede saturar el controlador USB y afectar indirectamente a la pool SSD, provocando que todas las VMs experimenten latencia.

Solución

1. Ajustar el tamaño del ARC

Limitar el ARC a un valor que deje suficiente RAM libre para el hipervisor y los procesos de las VMs. En un host con 64 GB, un rango de 12‑14 GB suele ser seguro.

echo 15000000000 > /sys/module/zfs/parameters/zfs_arc_max   # 15 GB

Para hacerlo permanente, añade a /etc/modprobe.d/zfs.conf:

options zfs zfs_arc_max=15000000000

2. Incrementar los hilos de ZVOL

Aumentar zvol_threads a un número que se aproxime a la mitad de los hilos de CPU disponibles. En un CPU de 32 hilos, 16 hilos por zvol es un buen punto de partida.

echo 16 > /sys/module/zfs/parameters/zvol_threads

Persistente en /etc/modprobe.d/zfs.conf:

options zfs zvol_threads=16

3. Añadir un SLOG de alta calidad

Instalar un pequeño SSD NVMe empresarial (por ejemplo, 400 GB) y asignarlo como SLOG para la pool que aloja los discos de VM. El comando:

zpool add NAS2 log /dev/nvme0n1p1

Esto desvía los writes síncronos al SLOG, reduciendo la latencia de la ZIL.

4. Optimizar los parámetros de I/O del kernel

Ajustar la profundidad de la cola y el scheduler para los dispositivos críticos:

echo 256 > /sys/block/nvme0n1/queue/nr_requests
echo mq-deadline > /sys/block/nvme0n1/queue/scheduler

Repetir para los discos HDD (/dev/sdX). En algunos casos bfq ofrece mejor aislamiento entre workloads mixtas.

5. Revisar la configuración de los discos de VM

  • Cache mode: usar writeback para discos en SSD y writethrough para discos en HDD.
  • IOThread: habilitar io_thread=1 en la definición de la VM cuando el disco está en un pool ZFS.
  • Throttle: aplicar límites de IOPS o throughput mediante qm set <vmid> -disk iops=5000 (o mbps) si la carga es predecible.

6. Separar workloads de alta y baja prioridad

Crear dos pools distintas: una con SSD + SLOG para VMs críticas y otra con HDD para backup. Montar los discos de backup con recordsize=1M y compression=off para minimizar la sobrecarga de metadatos.

7. Monitoreo continuo

Instalar zfs-stats o zpool iostat -v 5 y observar los valores de io_stall. Si el promedio supera 50 % durante más de 30 s, la configuración necesita ajuste.

Cuándo aplicar esta solución

  • Síntomas: I/O stall > 80 % en zpool iostat, VMs sin respuesta, pérdida de conectividad de servicios críticos.
  • Entorno: Proxmox con pools ZFS mixtos (SSD + HDD), al menos 8 GB de RAM reservados para el host, y carga de escritura sostenida (copias de backup, bases de datos, contenedores).
  • No aplica: Cuando el host ya está usando discos empresariales con SLOG dedicado y el ARC está configurado por debajo del 10 % de la RAM; en ese caso el problema suele estar en la aplicación o en la red, no en ZFS.

Código

# 1. Limitar ARC a 14 GB
echo 14000000000 > /sys/module/zfs/parameters/zfs_arc_max

# 2. Incrementar hilos de ZVOL a 16
echo 16 > /sys/module/zfs/parameters/zvol_threads

# 3. Añadir SLOG (SSD NVMe) a la pool NAS2
zpool add NAS2 log /dev/nvme0n1p1

# 4. Ajustar cola y scheduler del SSD
echo 256 > /sys/block/nvme0n1/queue/nr_requests
echo mq-deadline > /sys/block/nvme0n1/queue/scheduler

# 5. Configurar VM disk cache y IOThread (ejemplo VMID 101)
qm set 101 -scsi0 local-zfs:vm-101-disk-0,cache=writeback,io_thread=1

Verificación

  1. Ejecutar zpool iostat -v 5 y observar que io_stall se mantiene bajo 30 % durante operaciones intensas.
  2. Verificar que la RAM libre después del ARC sea al menos 20 % del total (free -h).
  3. En la VM, lanzar una carga de escritura (dd if=/dev/zero of=/mnt/disk/test bs=1M count=10240 oflag=direct) y comprobar que la latencia no supera 100 ms.
  4. Revisar los logs de Proxmox (journalctl -u pvedaemon) en busca de mensajes de “I/O stall” o “zvol thread limit”.

Notas adicionales

  • Evita colocar pools críticos en cajas USB; la latencia del controlador puede anular cualquier ajuste de ZFS.
  • Si el host tiene más de 128 GB de RAM, considera usar zfs_arc_max del 20‑25 % del total para mantener suficiente espacio de caché sin comprometer la memoria del hipervisor.
  • En entornos con muchas VMs pequeñas, la opción zfs_txg_timeout=5 puede reducir la duración de los grupos de transacciones y mejorar la reactividad.
  • Cuando uses discos de consumo como SLOG, monitoriza el desgaste (smartctl -a /dev/nvme0n1) y reemplázalos antes de que el nivel de vida caiga por debajo del 20 %.