Las mentiras de ZFS “No hay espacio”: solucionar el espacio sin borrados aleatorios

¿Te fue útil?

Son las 02:17. Tu despliegue está en vuelo. Una escritura falla con ENOSPC. Alguien ejecuta df -h y anuncia que el sistema de archivos solo está al 72% de uso. Felicidades: acabas de conocer uno de los trucos favoritos de ZFS—ser técnicamente correcto y operacionalmente frustrante.

Esto no es que ZFS sea “buggy”. Es ZFS siendo honesto sobre restricciones distintas a las que tu memoria muscular comprueba primero. El espacio en ZFS no es un solo número. Es un montón de reglas contables, realidades de metadatos y márgenes de seguridad que se hacen visibles solo cuando ya estás sangrando.

Qué significa realmente “no space left” en ZFS

Cuando una aplicación recibe ENOSPC en ZFS, no siempre se trata de bytes libres en bruto. ZFS es un sistema de archivos copy-on-write. Cada “sobrescritura” es una nueva escritura en otro lugar, seguida de actualizaciones de metadatos, y los bloques antiguos se liberan solo después de que el grupo de transacciones (TXG) se confirme—y solo si nada más hace referencia a esos bloques.

Así que “no hay espacio” puede significar cualquiera de estos:

  • El pool está realmente casi lleno y ZFS se protege de un colapso (slop space, restricciones del asignador).
  • Instantáneas están fijando bloques que pensabas haber borrado.
  • Reservas están consumiendo espacio que df no describe bien.
  • Cuotas están bloqueando un dataset aunque el pool tenga espacio.
  • Metadatos o sobrecarga de bloques pequeños están consumiendo el espacio asignable restante.
  • Fragmentación y llenado de metaslabs hacen que el “espacio libre” no sea utilizable para el patrón de escritura.
  • Zvol thin-provisioning parece espacioso hasta que no lo es; o parece lleno por restricciones de volsize/volblocksize.

Operacionalmente, deberías tratar “ZFS no hay espacio” como: “el asignador no puede satisfacer la petición dadas las políticas y referencias actuales.” Eso es distinto de “el disco está lleno”, y esa diferencia importa porque la solución rara vez es “rm -rf algo hasta que funcione”.

Una cita para tener en la cabeza cuando te tiente “simplemente borrar cosas” en producción: “La esperanza no es una estrategia.” — Rick Page

Guía rápida de diagnóstico (revisa esto antes de borrar algo)

Si no haces nada más, haz esto en orden. Está optimizado para velocidad, señal y no empeorar el incidente.

1) ¿Es a nivel de pool, dataset específico o un zvol?

  • Pool: zpool list y zpool status
  • Dataset: zfs list, zfs get quota,reservation,refreservation
  • Zvol: zfs list -t volume, zfs get volsize,volblocksize,refreservation

Decisión: si es una cuota/reserva de dataset, las soluciones son locales y rápidas. Si es a nivel de pool y está casi lleno, necesitas gestión de capacidad, no girar perillas.

2) ¿Las instantáneas están fijando espacio?

  • zfs list -t snapshot -o name,used,refer,creation -s used
  • zfs get usedbydataset,usedbysnapshots,usedbyrefreservation

Decisión: si las instantáneas dominan, borrar archivos no ayudará; borrar instantáneas (con cuidado) sí lo hará.

3) ¿Reservas o refreservations están robando espacio asignable?

  • zfs get -r reservation,refreservation
  • Comprueba refreservations inesperadamente grandes en zvols y datasets.

Decisión: elimina o reduce reservas solo si entiendes por qué existían. “Porque alguien leyó un blog de tuning” no es una razón válida.

4) ¿El pool está alcanzando slop space / dolor del asignador?

  • zpool list para ver porcentaje de capacidad alto.
  • zpool get autotrim y zpool status para errores de dispositivo y diseños degradados.

Decisión: si el pool está > ~80–90% y bajo presión de escritura, planifica alivio inmediato (liberar espacio real, añadir vdevs, mover cargas). No “desfragmentes ZFS”; eso no funciona así.

5) Busca consumidores “ocultos”: refreservation, dispositivos especiales, bloques pequeños y escrituras sync

  • zfs get -r special_small_blocks y confirma que no estés volcando metadatos en un special vdev de tamaño insuficiente.
  • zfs get recordsize,compression para datasets con escrituras pequeñas patológicas.

Decisión: si metadata/special está llena, puedes ver “no space” incluso cuando el pool principal parece estar bien. Eso requiere una solución dirigida.

Hechos interesantes y contexto histórico

  • ZFS nació en Sun Microsystems a mediados de los 2000, diseñado para acabar con la división “sistema de archivos + gestor de volúmenes” haciendo del almacenamiento una pila coherente.
  • Copy-on-write fue una elección deliberada de fiabilidad: ZFS evita sobrescribir en sitio para poder mantener la consistencia en disco incluso tras fallos.
  • El concepto de “pool” cambió el modelo antiguo: en lugar de que los sistemas de archivos posean particiones, los datasets son vistas baratas sobre un pool compartido, lo que hace la contabilidad más matizada.
  • Las instantáneas no son copias; son marcadores. Costan casi nada crearlas, pero pueden retener grandes cantidades de datos “borrados” indefinidamente.
  • El “slop space” de ZFS existe porque los pools casi llenos se comportan mal: la fragmentación crece, la asignación se vuelve costosa y en el peor caso puedes bloquearte sin espacio para liberar espacio.
  • Los metadatos son de primera clase y abundantes en ZFS: checksums, punteros de bloque, mapas de espacio y registros de intención significan que el sistema conoce mucho—y almacena mucho.
  • Ashift se hizo famoso por las unidades de 4K: elegir la alineación de sector incorrecta grava permanentemente tu capacidad utilizable y puede amplificar el dolor de “no hay espacio” mediante espacio desperdiciado.
  • OpenZFS se escindió y evolucionó en varias plataformas (Illumos, FreeBSD, Linux), y algunos comportamientos de reporte de espacio y características difieren sutilmente según la versión.
  • Los zvols hicieron popular a ZFS para virtualización, pero trajeron semántica de dispositivo-bloque (volsize, volblocksize, expectativas de discard/TRIM) a un mundo de sistema de archivos con instantáneas y COW.

Contabilidad del espacio que hace parecer a ZFS un mentiroso

df no es tu autoridad aquí

df pregunta al sistema de archivos montado qué cree que está disponible para ese montaje. En ZFS, el montaje es un dataset con propiedades: cuotas, reservas, refreservations y una vista de un pool que puede tener sus propias restricciones. Cuando ZFS dice “no hay espacio”, puede ser:

  • Que esté sin espacio en la vista del asignador del pool.
  • Bloqueado por una cuota de dataset aunque el pool tenga espacio.
  • Incapaz de asignar una región contigua de tamaño adecuado en un metaslab mayormente lleno (especialmente para ciertos tamaños de bloque y patrones).

Si estás depurando espacio en ZFS, zfs y zpool son la fuente de la verdad. df es el parte meteorológico pegado a tu ventana.

Instantáneas: la razón nº1 por la que los borrados no “liberan espacio”

En ZFS, un bloque se libera solo cuando nadie hace referencia a él. Una instantánea es una referencia. Así que si borras un archivo que existía cuando se tomó una instantánea, los bloques siguen referenciados por esa instantánea. Resultado: tu borrado “funciona”, tu aplicación está contenta y tu pool sigue lleno.

Las matemáticas del espacio se vuelven especialmente confusas porque ZFS informa el USED de una instantánea como el espacio que esa instantánea retiene de forma única en comparación con el estado actual del dataset. Ese número puede ser contraintuitivo. Las instantáneas pueden colectivamente fijar mucho espacio incluso cuando cada una parece pequeña, dependiendo del churn.

Reservas y refreservations: espacio que no puedes usar aunque sea “libre”

reservation garantiza espacio para un dataset. Eso significa que ZFS trata ese espacio como no disponible para otros.

refreservation garantiza espacio para los datos referenciados del dataset (excluyendo instantáneas). Se usa comúnmente en zvols para asegurar que siempre se puedan escribir, porque quedarse sin espacio bajo un dispositivo de bloque puede ser catastrófico para el sistema de archivos invitado.

Ambas son herramientas legítimas. También son una excelente forma de dejar al pool sin espacio si las configuras como si estuvieras aprovisionando un SAN empresarial en 2009.

Slop space: la política de ZFS de “llevar algo de efectivo en la cartera”

ZFS reserva un trozo del espacio del pool para que el sistema pueda seguir funcionando cerca del lleno: asignar metadatos, confirmar TXGs y recuperarse. Esto a veces se llama “slop space”. El comportamiento exacto depende de la versión y ajustes, pero el principio es estable: ZFS empezará a denegar asignaciones antes de llegar al 100% de uso bruto.

Esto no es ZFS dramatizando. Un sistema COW al 99% es una pesadilla para rendimiento y fiabilidad. Si ejecutas pools rutinariamente por encima de ~80–85% y luego te sorprende cuando las cosas se ponen raras, ese no es un problema de ZFS. Es un problema de decisiones de gestión.

Metadatos y bloques pequeños: puedes llenar el pool sin “archivos grandes”

ZFS almacena checksums, punteros de bloque, bloques indirectos, mapas de espacio, estructuras de directorio, atributos extendidos y más. Si creas millones de archivos pequeños, o guardas bloques pequeños altamente fragmentados, la sobrecarga de metadatos aumenta. Además, los bloques pequeños tienen proporcionalmente más punteros y pueden desperdiciar espacio debido a tamaños mínimos de asignación.

Si usas un vdev especial (para metadatos y opcionalmente bloques pequeños), puedes alcanzar “no espacio” cuando el special vdev se llena—aunque los vdevs de datos principales tengan mucho espacio. Ese es un sabor particular de emoción.

Fragmentación y metaslabs: “espacio libre” que no es asignable

ZFS asigna desde metaslabs. A medida que un pool se llena y churnea, el espacio libre puede dispersarse en fragmentos demasiado pequeños para los tamaños de bloque solicitados o demasiado costosos de asignar eficientemente. El asignador puede fallar una petición aun cuando el espacio total libre parece no cero.

Esto aparece más dolorosamente con:

  • Escrituras secuenciales grandes al final de la vida de un pool.
  • Imágenes de VM con escrituras aleatorias, instantáneas y clones.
  • Recordsize demasiado pequeño con alto churn.

Zvols: dispositivos de bloque con consecuencias ZFS

Los zvols parecen discos. La gente los trata como discos. Luego aprenden que los zvols están respaldados por datasets ZFS, lo que significa que instantáneas, refreservations, compresión y comportamiento COW pueden influir en el “no hay espacio”.

La provisión delgada lo hace más picante. Puedes tener un zvol con un gran volsize, mucho espacio aparente en el invitado y aun así bloquear el pool host hasta que las escrituras fallen. El invitado ve un disco que “debería funcionar”. El host ve un pool que no puede asignar. Ambos tienen razón. Todos están enfadados.

Chiste #1: La forma más rápida de aumentar el espacio libre en un pool ZFS es hacer una instantánea de tu carrera antes de tocar producción. Querrás algo a qué volver.

Tareas prácticas (comandos, salidas, decisiones)

Estas son las tareas que realmente ejecuto cuando ZFS informa ENOSPC o el pool se siente “lleno” en formas que no coinciden con df. Cada tarea incluye una muestra de salida realista y qué decisión tomar a partir de ella.

Tarea 1: Confirmar capacidad del pool y salud básica

cr0x@server:~$ zpool list
NAME   SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
tank  21.8T  19.9T  1.93T        -         -    62%    91%  1.00x  ONLINE  -

Qué significa: 91% de capacidad y 62% de fragmentación es una bandera roja. Incluso con 1.93T libres, la asignación puede fallar bajo presión y el rendimiento será horrible.

Decisión: trata esto como un incidente a nivel de pool. Tu reparación principal es liberar espacio significativo o añadir capacidad de vdev, no “borrar algunos logs”.

Tarea 2: Comprobar si una cuota de dataset causa ENOSPC local

cr0x@server:~$ zfs get -o name,property,value,source quota,refquota tank/app
NAME      PROPERTY  VALUE  SOURCE
tank/app  quota     2T     local
tank/app  refquota  none   default

Qué significa: el dataset tank/app está limitado a 2T independientemente del espacio libre del pool.

Decisión: si la app necesita legítimamente más, aumenta la cuota; si no, la app debe limpiarse dentro de su presupuesto.

Tarea 3: Revisar reservas y refreservations recursivamente

cr0x@server:~$ zfs get -r -o name,property,value,source reservation,refreservation tank | head
NAME            PROPERTY        VALUE  SOURCE
tank            reservation     none   default
tank            refreservation  none   default
tank/vm         reservation     none   default
tank/vm         refreservation  none   default
tank/vm/win01   reservation     none   default
tank/vm/win01   refreservation  500G   local
tank/vm/win02   reservation     none   default
tank/vm/win02   refreservation  500G   local

Qué significa: dos VMs tienen 500G cada una reservados. Eso son 1T de espacio del pool efectivamente fuera de la mesa para los demás.

Decisión: mantén refreservations solo donde necesites garantías estrictas. Si el pool está estresado, reducelas con cuidado y documenta por qué.

Tarea 4: Ver qué consume espacio por categoría

cr0x@server:~$ zfs get -o name,property,value -s local,default used,available,usedbysnapshots,usedbydataset,usedbyrefreservation tank/app
NAME      PROPERTY            VALUE
tank/app  used                1.89T
tank/app  available           110G
tank/app  usedbydataset       940G
tank/app  usedbysnapshots     850G
tank/app  usedbyrefreservation 0B

Qué significa: las instantáneas están reteniendo casi tanto como el dataset en vivo.

Decisión: borrar archivos en tank/app te decepcionará. Aborda las instantáneas primero: retención, replicación o eliminación dirigida de instantáneas.

Tarea 5: Identificar las instantáneas más grandes (por espacio “used”)

cr0x@server:~$ zfs list -t snapshot -o name,used,refer,creation -s used | tail -5
tank/app@auto-2025-12-20_0100   120G   820G  Sat Dec 20 01:00 2025
tank/app@auto-2025-12-21_0100   128G   812G  Sun Dec 21 01:00 2025
tank/app@auto-2025-12-22_0100   140G   800G  Mon Dec 22 01:00 2025
tank/app@auto-2025-12-23_0100   155G   785G  Tue Dec 23 01:00 2025
tank/app@auto-2025-12-24_0100   210G   740G  Wed Dec 24 01:00 2025

Qué significa: las instantáneas recientes están creciendo rápido; el churn es alto.

Decisión: revisa qué cambió (nueva carga, trabajo de compactación, fallo en rotación de logs) y ajusta la frecuencia/retención de instantáneas o el diseño del dataset.

Tarea 6: Simular un plan seguro de eliminación de instantáneas (revisión humana primero)

cr0x@server:~$ zfs list -t snapshot -o name -s creation | head -5
tank/app@auto-2025-11-01_0100
tank/app@auto-2025-11-02_0100
tank/app@auto-2025-11-03_0100
tank/app@auto-2025-11-04_0100
tank/app@auto-2025-11-05_0100

Qué significa: tienes una lista ordenada para proponer eliminaciones (por ejemplo, las más antiguas primero) alineada con la política.

Decisión: si debes eliminar instantáneas, borra las más antiguas primero a menos que tengas una “instantánea mala” conocida que fije un estado que ya no necesitas.

Tarea 7: Eliminar realmente instantáneas (con cuidado y preferiblemente en lotes)

cr0x@server:~$ sudo zfs destroy tank/app@auto-2025-11-01_0100
cr0x@server:~$ sudo zfs destroy tank/app@auto-2025-11-02_0100

Qué significa: las referencias de instantáneas se eliminan; el espacio se liberará una vez que no existan otras referencias y los TXGs se confirmen.

Decisión: después de algunos borrados, vuelve a comprobar zpool list y el available del dataset. Si nada mejora, otra restricción está en juego (clones, holds, reservas, special vdev).

Tarea 8: Comprueba si hay holds en instantáneas que impiden su eliminación

cr0x@server:~$ zfs holds tank/app@auto-2025-11-03_0100
NAME                             TAG              TIMESTAMP
tank/app@auto-2025-11-03_0100     keep             Thu Dec 12 09:14 2025

Qué significa: un hold llamado keep está fijando la instantánea.

Decisión: coordina con quien puso el hold (herramientas de backup/replicación). Elimina el hold solo cuando estés seguro de que ya no es necesario.

Tarea 9: Liberar un hold (explícitamente, con intención)

cr0x@server:~$ sudo zfs release keep tank/app@auto-2025-11-03_0100
cr0x@server:~$ sudo zfs destroy tank/app@auto-2025-11-03_0100

Qué significa: la instantánea ahora puede destruirse.

Decisión: si los holds aparecen inesperadamente, audita tu pipeline de backup/replicación. Los holds son buenos; los holds sorpresa son malos.

Tarea 10: Comprobar clones que mantienen instantáneas vivas

cr0x@server:~$ zfs get -o name,property,value clones tank/app@auto-2025-12-01_0100
NAME                            PROPERTY  VALUE
tank/app@auto-2025-12-01_0100   clones    tank/app-test

Qué significa: el dataset tank/app-test es un clone dependiente de esa instantánea. No puedes destruir la instantánea sin tratar el clone.

Decisión: destruye el clone, promuévelo o acepta que la instantánea debe permanecer. No “forces” a través de ello a menos que te guste explicar pérdida de datos.

Tarea 11: Identificar si un zvol está sobre-reservado o mal dimensionado

cr0x@server:~$ zfs list -t volume -o name,volsize,used,available tank/vm/win01
NAME           VOLSIZE  USED   AVAIL
tank/vm/win01   800G    610G   0B

Qué significa: AVAIL es 0B para la vista del dataset de ese volumen. Eso puede ser cuota/refreservation o una restricción a nivel de pool. También puede significar que tu zvol está en su límite respecto a las condiciones del pool.

Decisión: comprueba refreservation y la capacidad del pool. Si el pool está casi lleno, aumentar volsize puede ser arriesgado o imposible.

Tarea 12: Inspeccionar propiedades del zvol que afectan el comportamiento del espacio

cr0x@server:~$ zfs get -o name,property,value -s local,default volsize,volblocksize,compression,refreservation tank/vm/win01
NAME           PROPERTY       VALUE
tank/vm/win01  volsize        800G
tank/vm/win01  volblocksize   8K
tank/vm/win01  compression    lz4
tank/vm/win01  refreservation 800G

Qué significa: una refreservation de 800G garantiza el volsize completo. Genial para seguridad, brutal para pools compartidos.

Decisión: si necesitas garantías, mantenla. Si ejecutas un host de virtualización denso y te falta espacio, considera reducir refreservation y aceptar el riesgo operativo (con monitorización y margen).

Tarea 13: Comprobar si el pool tiene un checkpoint que consume espacio “fantasma”

cr0x@server:~$ zpool list
NAME   SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
tank  21.8T  19.9T  1.93T    1.2T         -    62%    91%  1.00x  ONLINE  -

Qué significa: existe un checkpoint que está reteniendo 1.2T de estado antiguo del pool. Ese es espacio real que no puedes recuperar hasta que el checkpoint se descarte.

Decisión: si no necesitas el checkpoint para rollback, libéralo. Si lo necesitas, has elegido operar con menos capacidad—asume esa decisión.

Tarea 14: Descartar un checkpoint (irreversible, así que piensa primero)

cr0x@server:~$ sudo zpool checkpoint -d tank
cr0x@server:~$ zpool list tank
NAME   SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
tank  21.8T  19.9T  1.93T        -         -    62%    91%  1.00x  ONLINE  -

Qué significa: el checkpoint se ha eliminado; ese espacio fijado ahora puede reclamarse según se liberen bloques.

Decisión: haz esto solo cuando estés seguro de que no necesitas la red de seguridad de rollback.

Tarea 15: Verificar salud y capacidad del special vdev (si tienes uno)

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

        NAME                         STATE     READ WRITE CKSUM
        tank                         ONLINE       0     0     0
          mirror-0                   ONLINE       0     0     0
            sda                      ONLINE       0     0     0
            sdb                      ONLINE       0     0     0
        special
          mirror-1                   ONLINE       0     0     0
            nvme0n1                  ONLINE       0     0     0
            nvme1n1                  ONLINE       0     0     0

errors: No known data errors

Qué significa: existe un special vdev. Si se llena, el pool puede volverse efectivamente “sin espacio” para metadatos/bloques pequeños.

Decisión: monitoriza el uso del special vdev agresivamente. Si está cerca del llenado, puede que necesites añadir capacidad special o ajustar la estrategia special_small_blocks.

Tarea 16: Comprobar la política de bloques pequeños que puede sobrecargar el special vdev

cr0x@server:~$ zfs get -r -o name,property,value special_small_blocks tank | head
NAME      PROPERTY             VALUE
tank      special_small_blocks 16K
tank/app  special_small_blocks 16K
tank/vm   special_small_blocks 0

Qué significa: cualquier bloque ≤16K para tank y tank/app va al special. Eso puede ser excelente para rendimiento y catastrófico para la capacidad special si la carga tiene muchos bloques pequeños.

Decisión: si special está apretado, reduce esto para datasets con mucho churn (las escrituras nuevas seguirán la nueva regla; los bloques antiguos no migrarán mágicamente).

Chiste #2: ZFS no “se come” tu espacio libre. Solo lleva recibos meticulosos, y tus borrados no son deducibles hasta que las instantáneas estén de acuerdo.

Tres microhistorias corporativas de las guerras por el espacio

Microhistoria 1: El incidente causado por una suposición equivocada

La empresa migraba un servicio heredado a contenedores. El almacenamiento era “simple”: un dataset ZFS montado en los nodos, con instantáneas horarias por seguridad. El playbook de on-call se heredó de los días de ext4: si el disco está lleno, borra logs antiguos y reinicia.

Durante un día de tráfico pico, las escrituras empezaron a fallar. La aplicación lanzó ENOSPC y entró en un bucle de crashes. El on-call comprobó df -h: 68% usado. Sospecharon un bug. Reiniciaron pods. Rotaron logs con más agresividad. Nada cambió.

Entonces alguien ejecutó zfs get usedbysnapshots y encontró que las instantáneas retenían más espacio que el dataset en vivo. El equipo había activado registro de peticiones verboso para depuración y desplegado un cambio que reescribía un gran JSON repetidamente. Cada hora, una nueva instantánea fijaba otra porción de ese churn. Borrar logs actuales no tocaba los bloques fijados.

La solución fue aburrida: eliminar un conjunto de instantáneas antiguas y reducir la frecuencia de instantáneas para ese dataset. La lección del postmortem fue afilada: “df no es una autoridad en ZFS” se convirtió en una línea literal del runbook, y el equipo añadió un gráfico de monitorización para used by snapshot y capacidad del pool.

Microhistoria 2: La optimización que salió mal

Un equipo de plataforma de virtualización quería rendimiento más rápido para VMs. Añadieron un special vdev en mirrored NVMe para acelerar metadatos y bloques pequeños. También configuraron special_small_blocks=32K a nivel de pool porque funcionó bien en laboratorio. Todos celebraron; los gráficos mejoraron.

Meses después, un nuevo sistema de builds internas cayó sobre el mismo pool. Creó océanos de archivos pequeños, los churneó constantemente y reescribía pequeños blobs. El special vdev empezó a llenarse mucho más rápido que los vdevs de datos principales. Nadie lo notó porque el pool todavía mostraba “terabytes libres” y la única alerta era una “capacidad del pool” genérica.

Entonces llegó la diversión: errores aparentemente aleatorios de “no space left” en servicios no relacionados. Las asignaciones de metadatos fallaban porque el special vdev estaba apretado. Algunos datasets ni siquiera usaban bloques pequeños, pero aún necesitan metadatos. El equipo de almacenamiento vio espacio libre en los vdevs principales y fue culpado por “ZFS mintiendo otra vez”.

La solución final requirió añadir más capacidad special y restringir special_small_blocks solo a los datasets que realmente se beneficiaban. La lección fue dolorosa pero clara: los ajustes “rápidos” tienen radio de impacto. No los despliegues pool-wide solo porque el gráfico del benchmark se ve bonito.

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

Un sistema cercano a finanzas almacenaba informes diarios y también los servía a clientes. El equipo ejecutaba ZFS con cuotas estrictas por dataset y una política de que el pool nunca superara un techo operativo. Consideraban 80–85% como “lleno” para el pool, porque ya habían visto qué ocurre después.

Un viernes, un proveedor upstream cambió un formato de feed y el parser empezó a duplicar datos. El consumo de almacenamiento se disparó. El servicio seguía sano, pero el pool subía rápidamente hacia el techo. Las alertas saltaron temprano porque estaban orientadas a capacidad del pool y crecimiento de instantáneas—no a fallos visibles por clientes.

La respuesta del equipo fue poco glamorosa: detuvieron la ingestión, mantuvieron la parte de servicio en funcionamiento y usaron instantáneas para preservar evidencia para depuración. Como el pool aún tenía margen, pudieron hacerlo sin provocar pánico del asignador o borrados de emergencia. Arreglaron el parser, repusieron los datos correctos y reanudaron.

El postmortem fue corto y algo ufano. La “práctica aburrida” fue simplemente mantener margen, aplicar cuotas y monitorizar el espacio usado por instantáneas. Nadie tuvo que borrar nada al azar. Nadie tuvo que explicar por qué los backups habían desaparecido. A veces la mejor ingeniería es negarse a operar al límite.

Errores comunes: síntomas → causa raíz → arreglo

1) “df muestra mucho espacio, pero las escrituras fallan”

Síntoma: la aplicación recibe ENOSPC; df -h muestra espacio cómodo.

Causa raíz: cuota de dataset, reservation/refreservation, o slop space del pool / restricciones del asignador.

Arreglo: comprueba zfs get quota,refquota,reservation,refreservation y zpool list. Ajusta la propiedad relevante o libera espacio del pool.

2) “Borré mucho, pero nada se liberó”

Síntoma: quitas archivos; la asignación del pool no baja.

Causa raíz: instantáneas (o clones) fijando los bloques.

Arreglo: cuantifica usedbysnapshots. Identifica instantáneas pesadas, holds y clones; elimina instantáneas según la retención, o elimina/promueve clones.

3) “El pool está al 90% y de repente todo está lento y falla”

Síntoma: picos de latencia, asignaciones que fallan intermitentemente, scrubs lentos, IO aleatorio miserable.

Causa raíz: comportamiento del asignador cerca del lleno + fragmentación. COW necesita espacio para respirar.

Arreglo: libera espacio significativo (no gigabytes, sino puntos porcentuales relevantes), añade capacidad de vdev o migra cargas. Luego aplica y hace cumplir un techo.

4) “Añadimos un special vdev y ahora ‘no space’ ocurre temprano”

Síntoma: el pool tiene mucho espacio bruto; cargas ricas en metadatos fallan; los errores parecen agotamiento de espacio.

Causa raíz: el special vdev está lleno o casi lleno por la política de metadatos/bloques pequeños.

Arreglo: monitoriza y expande el special vdev; restringe special_small_blocks a datasets específicos; deja de enviar bloques churny allí a menos que lo hayas dimensionado para eso.

5) “El sistema invitado del zvol dice que tiene espacio, el host dice ENOSPC”

Síntoma: escrituras de VM fallan; el invitado tiene espacio libre; el pool host está casi lleno o thin-provisioned.

Causa raíz: provisión delgada + COW + instantáneas + falta de margen; o un esquema de refreservation que deja al pool sin recursos.

Arreglo: añade margen, reduce churn de instantáneas, asegura que discard/TRIM esté configurado end-to-end donde sea apropiado, y decide si refreservation es necesaria.

6) “Activamos dedup para ahorrar espacio; ahora no tenemos espacio”

Síntoma: la contabilidad de capacidad empeoró; la presión de memoria aumentó; el espacio se comporta de forma impredecible.

Causa raíz: dedup añade metadatos y coste operativo; en muchas cargas es una trampa a menos que se diseñe para ello.

Arreglo: no actives dedup a la ligera. Si ya lo hiciste, mide y considera migrar datos a un dataset/pool sin dedup en lugar de intentar “desactivarlo” como si fuera un deshacer mágico.

Listas de verificación / plan paso a paso

Checklist A: “Detener la hemorragia” durante un incidente ENOSPC

  1. Confirmar alcance: pool vs dataset vs zvol. Ejecuta zpool list y zfs list.
  2. Congelar churn: pausa el trabajo que genera escrituras (ingest, compactación, backups, artefactos de CI). Los incidentes de espacio empeoran con churn.
  3. Encontrar la restricción: cuotas/reservas/instantáneas/special vdev. No adivines.
  4. Recuperar espacio de forma segura: elimina instantáneas según la política (las más antiguas primero), reduce reservas o mueve un dataset a otro pool.
  5. Verificar recuperación: vuelve a comprobar CAP del pool, available del dataset y el éxito de escritura de la aplicación.
  6. Documentar lo que cambiaste: eliminaciones de instantáneas, cambios de propiedad, cualquier cosa. El tú del futuro es un stakeholder.

Checklist B: “No volver a pasar”

  1. Establece un techo operativo para el uso del pool (comúnmente 80–85% según la carga) y alerta antes de alcanzarlo.
  2. Monitoriza el crecimiento de instantáneas por dataset, no solo el uso del pool.
  3. Usa cuotas intencionadamente para vecinos ruidosos y trabajos que se salen de control.
  4. Usa reservas con parsimonia y solo donde las garantías valgan el coste en un pool compartido.
  5. Para virtualización: decide la política sobre provisión delgada; si la permites, exige margen y monitorización agresiva.
  6. Revisa la estrategia de special vdev como un plan de capacidad, no como un ajuste rápido.
  7. Ejecuta scrubs regularmente y trata cualquier error de dispositivo como urgente. Incidentes de espacio y de fiabilidad suelen llegar juntos.

Preguntas frecuentes

1) ¿Por qué ZFS dice “no hay espacio” antes de que el pool alcance el 100%?

Porque ZFS mantiene espacio de reserva (slop space) y necesita espacio asignable para metadatos y confirmaciones de TXG. Cerca del 100%, un sistema COW puede atraparse a sí mismo.

2) ¿Por qué borrar archivos no liberó espacio?

Probablemente las instantáneas (o clones) aún referencian esos bloques. Comprueba zfs get usedbysnapshots y lista instantáneas por used.

3) ¿Es seguro borrar instantáneas para liberar espacio?

Por lo general, sí—si entiendes por qué existen (backup, replicación, puntos de rollback). La parte insegura es borrar las equivocadas sin coordinar los requisitos de retención.

4) ¿Cuál es la diferencia entre reservation y refreservation?

reservation reserva espacio para el dataset incluyendo instantáneas. refreservation reserva espacio para los datos referenciados del dataset solamente, usado a menudo para seguridad en zvols.

5) ¿La fragmentación por sí sola puede causar ENOSPC?

Puede contribuir. Cuando las metaslabs están saturadas y fragmentadas, la asignación para ciertos tamaños de bloque puede fallar aun con algo de espacio libre restante, especialmente bajo churn intenso.

6) ¿La compresión ayuda con “no hay espacio”?

La compresión puede reducir el espacio asignado y retrasar el dolor, pero no es un salvavidas cuando ya estás cerca del lleno y el churn de instantáneas fija bloques antiguos.

7) ¿Por qué el USED de una instantánea parece pequeño aun cuando las instantáneas retienen mucho espacio?

El USED de la instantánea es “único para esa instantánea” en relación con el estado actual del dataset. Muchas instantáneas pueden parecer modestas individualmente mientras que colectivamente fijan mucho churn histórico.

8) ¿Debería simplemente añadir un disco más grande al pool?

Añade capacidad correctamente: los pools ZFS crecen añadiendo vdevs, no cambiando un único disco (a menos que reemplaces cada disco en un vdev y expandas). Las soluciones de capacidad son buenas cuando se planifican, no cuando se hacen por pánico.

9) ¿Un special vdev lleno puede causar fallos de escritura en todo el pool?

Sí. Si no se pueden asignar metadatos (o bloques pequeños dirigidos al special), las escrituras normales pueden fallar. La capacidad special no es opcional una vez que dependes de ella.

10) ¿Cuánto espacio libre debo mantener en un pool ZFS?

Suficiente para no jugar a la ruleta del asignador. Para muchas cargas de producción, considera 80–85% como “lleno”. Para pools con muchas VMs y escrituras aleatorias, sé aún más conservador.

Conclusión: siguientes pasos que puedes hacer hoy

Si estás en un incidente ahora: detén el churn, identifica si las instantáneas o las reservas son los verdaderos consumidores de espacio y libera espacio de una manera que puedas explicar en un postmortem. El borrado aleatorio es cómo cambias un incidente por falta de espacio por un incidente de pérdida de datos.

Si no estás en un incidente (el lujo raro): establece un techo de capacidad para el pool, monitoriza usedbysnapshots y reservas, y ensaya los pasos de “diagnóstico rápido” para no aprenderlos a las 2 a.m. El objetivo no es hacer que ZFS deje de “mentir”. El objetivo es hablar el idioma de ZFS antes de que empiece a gritar.

← Anterior
Time-outs de NFS en Proxmox: opciones de montaje que mejoran la estabilidad
Siguiente →
Debian 13: nf_conntrack tabla llena — por qué fallan las conexiones y cómo ajustar de forma segura

Deja un comentario