Problema

En entornos Proxmox con ZFS como pool de almacenamiento, es frecuente observar picos de consumo de RAM que superan la capacidad física disponible. Cuando el sistema simultáneamente utiliza swap (ya sea una partición o un volumen ZFS), la presión de memoria puede desencadenar cuelgues del host, reinicios inesperados o mensajes de error como “Purging GPU Memory”. El patrón típico es:

  • Un workload intensivo (por ejemplo, una copia de seguridad nocturna) dispara la expansión del ARC de ZFS.
  • El ARC crece sin límite, ocupando la mayor parte de la RAM.
  • El kernel empieza a paginar hacia swap, que a su vez consume I/O del mismo pool ZFS.
  • La combinación de alta latencia de swap y falta de RAM libre lleva al host a un estado inestable.

Este comportamiento no es exclusivo de una configuración concreta; cualquier nodo Proxmox que use ZFS sin restricciones de ARC y que tenga swap habilitado puede experimentar los mismos síntomas.

Causa

1. Ausencia de límites en ZFS ARC

ZFS gestiona su caché de lectura (ARC) de forma dinámica, ajustando su tamaño según la disponibilidad de memoria. En instalaciones modernas, el valor por defecto suele ser un porcentaje amplio del total de RAM (hasta 80 %). Cuando el ARC no está limitado, cualquier carga de I/O intensiva (por ejemplo, zfs send/receive o rsync sobre ZFS) puede inflar el ARC rápidamente.

2. Swap en el mismo pool ZFS

Crear una partición de swap o un volumen ZFS (swapfile o zvol) dentro del mismo pool que aloja los datasets críticos introduce una dependencia cíclica: el ARC compite por la misma memoria que el swap necesita para almacenar páginas evictadas. Además, el acceso a swap en ZFS implica escrituras en el mismo pool, aumentando la carga de I/O y reduciendo el rendimiento del propio ARC.

3. Parámetro vm.swappiness alto

El kernel decide cuánto usar swap mediante vm.swappiness. Un valor por defecto (60) favorece el uso de swap incluso cuando todavía hay RAM libre, lo que acelera la presión de memoria en entornos con ARC sin límites.

Solución

La estrategia se basa en tres pilares: limitar el ARC, eliminar o reubicar swap y ajustar swappiness. Cada pilar es independiente, pero su combinación brinda la mayor estabilidad.

A. Definir límites de ARC

  1. Calcular valores razonables
    Un buen punto de partida es reservar entre el 10 % y el 20 % de la RAM para el sistema y los contenedores, y usar el resto como máximo para ARC. En un host de 64 GB, un zfs_arc_max de 8 GB y un zfs_arc_min de 2 GB suele ser suficiente.

  2. Persistir la configuración
    Añadir las opciones al archivo de módulos garantiza que los valores se apliquen en cada arranque.

B. Reubicar o eliminar swap

  • Preferir swap en un disco distinto (por ejemplo, una unidad SSD dedicada) o, si la carga de memoria es controlada, eliminarlo por completo.
  • Si se necesita swap por seguridad, crear un archivo de swap fuera del pool ZFS evita la sobrecarga de I/O dentro del mismo pool.

C. Ajustar swappiness

Reducir vm.swappiness a 10 o 5 indica al kernel que prefiera mantener las páginas en RAM antes de recurrir a swap, reduciendo la frecuencia de paginación bajo carga.

Cuándo aplicar esta solución

Se recomienda cuando se cumplan al menos uno de los siguientes criterios:

  • Picos de uso de RAM que superan el 70 % durante tareas programadas (backups, replicación, snapshots).
  • Presencia de swap dentro del pool ZFS o en la misma unidad de arranque.
  • Mensajes de kernel relacionados con “out of memory” o “OOM killer” en los logs.
  • Inestabilidad del host que ocurre en horarios de alta actividad de I/O.

No es necesario si:

  • El host tiene más RAM de la que cualquier carga prevista pueda consumir (p.ej., 256 GB con workloads que nunca superan 32 GB).
  • Se usa un pool ZFS exclusivamente para datos y el swap está en un disco independiente.
  • El ARC está ya limitado mediante zfs_arc_max y zfs_arc_min y se ha verificado que no alcanza esos límites.

Código

# 1. Limitar ARC (crear/editar /etc/modprobe.d/zfs.conf)
cat <<EOF > /etc/modprobe.d/zfs.conf
options zfs zfs_arc_min=2147483648   # 2 GB
options zfs zfs_arc_max=8589934592   # 8 GB
EOF

# 2. Regenerar initramfs y actualizar bootloader de Proxmox
update-initramfs -u -k all
proxmox-boot-tool refresh

# 3. Desactivar swap en ZFS (ejemplo: zvol llamado rpool/swap)
zfs destroy -r rpool/swap
swapoff /dev/zvol/rpool/swap

# 4. Crear swap externo (ejemplo: archivo en /mnt/ssd)
dd if=/dev/zero of=/mnt/ssd/swapfile bs=1M count=8192
chmod 600 /mnt/ssd/swapfile
mkswap /mnt/ssd/swapfile
swapon /mnt/ssd/swapfile
echo '/mnt/ssd/swapfile none swap sw 0 0' >> /etc/fstab

# 5. Reducir swappiness
sysctl -w vm.swappiness=10
echo 'vm.swappiness=10' >> /etc/sysctl.d/99-swappiness.conf

Verificación

  1. Comprobar límites de ARC

    cat /sys/module/zfs/parameters/zfs_arc_max
    cat /sys/module/zfs/parameters/zfs_arc_min
    

    Los valores deben coincidir con los configurados (8 GB y 2 GB).

  2. Validar que el swap externo está activo

    swapon --show
    

    La salida debe listar el archivo creado en /mnt/ssd/swapfile.

  3. Monitorizar uso de RAM durante una carga típica
    Ejecutar htop o free -h mientras se realiza una copia de seguridad. El ARC no debe superar el límite establecido y la columna “Swap” debe permanecer cercana a 0 %.

  4. Revisar logs

    journalctl -k | grep -i oom
    

    No deberían aparecer eventos de OOM después de aplicar los cambios.

Notas adicionales

  • En Proxmox, los contenedores LXC comparten la misma memoria del host. Si varios contenedores ejecutan workloads intensivos, ajustar zfs_arc_max a un valor más bajo (p.ej., 4 GB) puede ser necesario.
  • Cuando se elimina un swap ZFS, es recomendable ejecutar zpool scrub para asegurarse de que el pool no tenga bloques pendientes de limpieza.
  • Si el host dispone de varias interfaces de red y la replicación ZFS se realiza a través de la red, la latencia de I/O puede empeorar la presión de memoria. En esos casos, combinar la limitación de ARC con QoS de red ayuda a estabilizar el rendimiento.
  • Cambios en zfs_arc_min y zfs_arc_max solo surten efecto después de reiniciar el host o recargar el módulo ZFS. En producción, planifique una ventana de mantenimiento para aplicar la actualización.