ZFS zfs hold: el pasador de seguridad que bloquea la eliminación accidental

¿Te fue útil?

ZFS tiene la reputación de ser “difícil de estropear”, y en gran medida es cierto hasta que te encuentras con el único comando que puede hacer desaparecer historia: la destrucción de snapshots. Las instantáneas son baratas, rápidas y peligrosamente fáciles de eliminar en bloque—especialmente cuando alguien está persiguiendo presión de espacio y el pager ya está sonando.

zfs hold es el mecanismo pequeño que convierte snapshots de “ups” en “hoy no”. No cifra nada, no mueve datos y no te salvará de todos los fallos posibles. Pero hace una cosa excepcionalmente bien: hace que una snapshot sea indestructible a menos que el hold se libere deliberadamente. En producción, esa es la diferencia entre un incidente recuperable y un cambio de carrera.

Qué hace realmente zfs hold (y lo que no hace)

Una snapshot de ZFS es inmutable, pero no indestructible. Puedes destruirla, y si era lo único que mantenía referenciados bloques antiguos, esos bloques quedan libres para ser reutilizados. En un pool ocupado, “reutilizado” puede significar “perdido” rápidamente.

zfs hold adjunta uno o más “holds” (piensa: etiquetas) a una snapshot. Mientras exista al menos un hold, la snapshot no puede ser destruida. Ni por ti, ni por un script, ni por un compañero bienintencionado con un comodín y una fecha límite. El comando destroy fallará con un mensaje que apunta a los holds.

Lo que no hace:

  • No impide que la snapshot consuma espacio. De hecho, puede mantener espacio retenido por más tiempo (ese es el objetivo).
  • No impide que alguien destruya todo el dataset o pool por otros medios si tiene privilegios suficientes y está decidido.
  • No reemplaza una política real de retención, copias de seguridad, verificación de replicación o control de acceso.

Aquí está el modelo mental que uso:

  • Snapshot = una vista congelada de los bloques del dataset en un momento dado.
  • Hold = una nota adhesiva en esa snapshot que dice “no eliminar hasta que se cumplan estas condiciones”.
  • Liberar = retirar la nota adhesiva; una vez que todas las notas se retiran, la eliminación vuelve a ser posible.

Primer chiste (manténlo corto, como tu presupuesto de outage): Un hold es la única cosa “pegajosa” en almacenamiento que en realidad quieres que sea pegajosa.

Cómo funcionan los holds internamente

Los holds se implementan como metadata en la snapshot. Cada hold tiene un nombre (a menudo llamado etiqueta) y está asociado a esa snapshot. Pueden existir múltiples holds simultáneamente—común en organizaciones donde backups, replicación y retención legal quieren tener voz.

Semántica de etiquetas: por qué los nombres importan

La etiqueta no es solo decorativa. Se convierte en tu punto de operación para auditorías, resolución de problemas y automatización segura. Si nombras holds como “keep”, te arrepentirás cuando estés mirando 40TB de snapshots atadas y nadie recuerde para qué servía ese “keep”.

Una buena etiqueta incluye:

  • Propietario/sistema: replication, backup, legal, migration
  • Ámbito o destino: to-dr, to-s3-gw, pre-upgrade
  • ID de ticket o cambio opcional: chg12345 (si tu entorno las usa)

Holds vs propiedades vs “simplemente no lo hagas”

Sí, puedes limitar quién puede ejecutar zfs destroy. Deberías. Pero los límites de privilegios se difuminan bajo presión: accesos de emergencia, cuentas break-glass, automatización ejecutándose como root, ingenieros on-call con derechos elevados. Un hold añade un paso intencional extra: “Debo liberar el hold primero.” Ese paso extra es donde muchas catástrofes mueren en silencio.

Holds y replicación

Los holds son especialmente útiles con replicación porque la replicación crea una cadena de dependencia: a menudo necesitas que una snapshot específica exista el tiempo suficiente para completar sends incrementales, para inicializar un nuevo destino o para garantizar una ventana de restauración consistente. La automatización que fija “la última snapshot replicada con éxito” es una práctica clásica, aburrida y correcta.

Qué pasa cuando intentas destruir una snapshot con hold

ZFS no discute; se niega. La destrucción falla con un error que referencia los holds. Esto es bueno. También es una fuente frecuente de confusión cuando alguien espera que su trabajo de limpieza recupere espacio inmediatamente.

Segundo chiste: zfs hold es como poner una etiqueta de “No desconectar” en el cable de alimentación del servidor—molesto hasta el día que deja de serlo.

Hechos interesantes y contexto

Algunos puntos cortos y concretos que ayudan a explicar por qué existen los holds y por qué se usan como se usan:

  1. Las snapshots de ZFS no son copias. Son un conjunto de punteros a bloques; los “datos antiguos” permanecen referenciados hasta que nada los apunta.
  2. Los holds son por snapshot y basados en etiquetas. Varios equipos pueden fijar la misma snapshot de forma independiente sin coordinarse—hasta que llega el momento de eliminarla.
  3. La presión de espacio hace a los humanos peligrosos. En operaciones reales, la mayoría de las eliminaciones catastróficas ocurren durante una emergencia de almacenamiento, no en planificación tranquila.
  4. La replicación depende de la línea de tiempo. Los sends incrementales requieren una base común; si eliminas la base equivocada, tu próxima replicación se convertirá en un reenvío completo.
  5. Las herramientas de auto-snapshot pueden crear “tormentas de snapshots”. Si la retención falla, obtienes miles de snapshots—luego alguien recurre a un destroy con comodines. Los holds son el cinturón de seguridad.
  6. Los holds no son lo mismo que los bookmarks. Los bookmarks pueden preservar puntos de envío incrementales sin preservar todos los bloques como lo hacen las snapshots; los holds mantienen viva la snapshot en sí.
  7. Los holds son baratos hasta que no lo son. La metadata es pequeña; el costo es los bloques referenciados que no puedes liberar mientras la snapshot esté fijada.
  8. “No se puede destruir” es una característica, no un bug. La mayoría de los sistemas tratan la eliminación como final; ZFS te da un mecanismo deliberado de “¿estás seguro de verdad?” con el que puedes automatizar.

Tareas prácticas: comandos que realmente ejecutarás

El objetivo aquí no es mostrar sintaxis por presumir. Es construir memoria operativa: cómo aplicar holds, encontrarlos, interpretar errores e integrar holds en la retención y replicación sin inmovilizar tu pool.

Tarea 1: Crear una snapshot con un nombre operativo significativo

cr0x@server:~$ sudo zfs snapshot tank/app@pre-upgrade-2025-12-25_0100
cr0x@server:~$ sudo zfs list -t snapshot -o name,used,refer,mountpoint -r tank/app | tail -n 3
NAME                                   USED  REFER  MOUNTPOINT
tank/app@auto-2025-12-25_0000           12M   48G    -
tank/app@auto-2025-12-25_0030           8M    48G    -
tank/app@pre-upgrade-2025-12-25_0100    0B    48G    -

Interpretación: La snapshot se crea al instante. USED puede mostrar 0B al crearla porque todavía no se han desviado bloques.

Tarea 2: Poner un hold en esa snapshot

cr0x@server:~$ sudo zfs hold change:pre-upgrade tank/app@pre-upgrade-2025-12-25_0100
cr0x@server:~$ sudo zfs holds tank/app@pre-upgrade-2025-12-25_0100
NAME                                   TAG                 TIMESTAMP
tank/app@pre-upgrade-2025-12-25_0100    change:pre-upgrade  Fri Dec 25 01:00 2025

Interpretación: La snapshot ahora tiene una etiqueta. No puede ser destruida hasta que esta etiqueta se libere (y cualquier otra etiqueta también).

Tarea 3: Demostrar que el hold bloquea la destrucción

cr0x@server:~$ sudo zfs destroy tank/app@pre-upgrade-2025-12-25_0100
cannot destroy snapshot tank/app@pre-upgrade-2025-12-25_0100: snapshot has holds

Interpretación: Ese error es tu pasador de seguridad haciendo su trabajo. Tu automatización de limpieza debería tratar esto como “saltar” y no como “reintentar para siempre”.

Tarea 4: Añadir un segundo hold (múltiples interesados)

cr0x@server:~$ sudo zfs hold replication:to-dr tank/app@pre-upgrade-2025-12-25_0100
cr0x@server:~$ sudo zfs holds tank/app@pre-upgrade-2025-12-25_0100
NAME                                   TAG                 TIMESTAMP
tank/app@pre-upgrade-2025-12-25_0100    change:pre-upgrade  Fri Dec 25 01:00 2025
tank/app@pre-upgrade-2025-12-25_0100    replication:to-dr   Fri Dec 25 01:02 2025

Interpretación: La destrucción está bloqueada hasta que ambas etiquetas se liberen. Así es como los equipos evitan pisarse unos a otros.

Tarea 5: Liberar exactamente un hold y confirmar que sigue protegida

cr0x@server:~$ sudo zfs release change:pre-upgrade tank/app@pre-upgrade-2025-12-25_0100
cr0x@server:~$ sudo zfs holds tank/app@pre-upgrade-2025-12-25_0100
NAME                                   TAG                TIMESTAMP
tank/app@pre-upgrade-2025-12-25_0100    replication:to-dr  Fri Dec 25 01:02 2025

cr0x@server:~$ sudo zfs destroy tank/app@pre-upgrade-2025-12-25_0100
cannot destroy snapshot tank/app@pre-upgrade-2025-12-25_0100: snapshot has holds

Interpretación: Liberar una etiqueta no elimina la protección si queda otra. Este es el mecanismo que hace sensata la retención compartida.

Tarea 6: Liberar el hold restante y destruir la snapshot

cr0x@server:~$ sudo zfs release replication:to-dr tank/app@pre-upgrade-2025-12-25_0100
cr0x@server:~$ sudo zfs destroy tank/app@pre-upgrade-2025-12-25_0100
cr0x@server:~$ sudo zfs list -t snapshot -r tank/app | grep pre-upgrade || echo "snapshot removed"
snapshot removed

Interpretación: Una vez que se libera el último hold, la eliminación se comporta con normalidad.

Tarea 7: Aplicar holds recursivamente a un árbol de datasets (con cuidado)

cr0x@server:~$ sudo zfs snapshot -r tank/projects@quarterly-freeze-2025Q4
cr0x@server:~$ sudo zfs hold -r legal:q4-retention tank/projects@quarterly-freeze-2025Q4
cr0x@server:~$ sudo zfs holds -r tank/projects@quarterly-freeze-2025Q4 | head
NAME                                               TAG                 TIMESTAMP
tank/projects@quarterly-freeze-2025Q4              legal:q4-retention  Fri Dec 25 02:00 2025
tank/projects/alpha@quarterly-freeze-2025Q4        legal:q4-retention  Fri Dec 25 02:00 2025
tank/projects/beta@quarterly-freeze-2025Q4         legal:q4-retention  Fri Dec 25 02:00 2025
tank/projects/beta/builds@quarterly-freeze-2025Q4  legal:q4-retention  Fri Dec 25 02:00 2025

Interpretación: Esto fija snapshots a través del árbol. Es potente y potencialmente caro en retención de espacio. Lo haces a propósito, no por accidente.

Tarea 8: Encontrar qué holds bloquean la eliminación (el comando “¿por qué no muere?”)

cr0x@server:~$ sudo zfs destroy tank/projects/alpha@quarterly-freeze-2025Q4
cannot destroy snapshot tank/projects/alpha@quarterly-freeze-2025Q4: snapshot has holds

cr0x@server:~$ sudo zfs holds tank/projects/alpha@quarterly-freeze-2025Q4
NAME                                         TAG                 TIMESTAMP
tank/projects/alpha@quarterly-freeze-2025Q4  legal:q4-retention  Fri Dec 25 02:00 2025

Interpretación: Este es el primer punto de parada en cualquier incidente de limpieza: identifica la etiqueta exacta y decide si puedes eliminarla de forma segura.

Tarea 9: Auditar holds en todo un árbol de datasets

cr0x@server:~$ sudo zfs holds -r tank/projects | awk 'NR==1 || $2 ~ /legal:|replication:|backup:/ {print}'
NAME                                               TAG                 TIMESTAMP
tank/projects@quarterly-freeze-2025Q4              legal:q4-retention  Fri Dec 25 02:00 2025
tank/projects/alpha@quarterly-freeze-2025Q4        legal:q4-retention  Fri Dec 25 02:00 2025
tank/projects/beta@quarterly-freeze-2025Q4         legal:q4-retention  Fri Dec 25 02:00 2025

Interpretación: Estás construyendo un inventario de snapshots “fijadas”. En entornos grandes, esto es la diferencia entre una política de retención manejable y un ático embrujado.

Tarea 10: Estimar impacto en espacio: encontrar deltas pesados de snapshots

cr0x@server:~$ sudo zfs list -t snapshot -o name,used,creation -s used -r tank/projects | tail -n 10
tank/projects/beta@auto-2025-12-20_0000     18G  Sat Dec 20 00:00 2025
tank/projects/beta@auto-2025-12-21_0000     22G  Sun Dec 21 00:00 2025
tank/projects/beta@quarterly-freeze-2025Q4  35G  Fri Dec 25 02:00 2025

Interpretación: Las snapshots con alto USED están fijando muchos bloques únicos. Si están en hold, ese espacio probablemente sea innegociable hasta que se libere el hold.

Tarea 11: Usar holds para proteger snapshots “última buena base” de replicación

cr0x@server:~$ sudo zfs snapshot tank/app@replica-base-2025-12-25_0300
cr0x@server:~$ sudo zfs hold replication:last-good tank/app@replica-base-2025-12-25_0300
cr0x@server:~$ sudo zfs holds tank/app@replica-base-2025-12-25_0300
NAME                                   TAG                   TIMESTAMP
tank/app@replica-base-2025-12-25_0300   replication:last-good Fri Dec 25 03:00 2025

Interpretación: Este es un patrón común de replicación: mantener siempre una base conocida buena fijada hasta que la siguiente replicación confirme éxito.

Tarea 12: Rotar limpiamente una snapshot “última buena” después del éxito

cr0x@server:~$ sudo zfs holds -r tank/app | grep replication:last-good
tank/app@replica-base-2025-12-25_0300   replication:last-good Fri Dec 25 03:00 2025

cr0x@server:~$ sudo zfs snapshot tank/app@replica-base-2025-12-25_0400
cr0x@server:~$ sudo zfs hold replication:last-good tank/app@replica-base-2025-12-25_0400

cr0x@server:~$ sudo zfs release replication:last-good tank/app@replica-base-2025-12-25_0300
cr0x@server:~$ sudo zfs destroy tank/app@replica-base-2025-12-25_0300
cr0x@server:~$ sudo zfs holds tank/app@replica-base-2025-12-25_0400
NAME                                   TAG                   TIMESTAMP
tank/app@replica-base-2025-12-25_0400   replication:last-good Fri Dec 25 04:00 2025

Interpretación: Observa el orden: crear nueva base → fijarla → liberar hold de la base vieja → destruir la base vieja. Si inviertes ese orden bajo pérdida de paquetes, acabarás pagándolo con un reenvío completo.

Tarea 13: Destruir snapshots saltando las que están en hold (patrón seguro de limpieza)

cr0x@server:~$ for s in $(sudo zfs list -H -t snapshot -o name -r tank/app | head -n 5); do
>   sudo zfs destroy "$s" 2>&1 | sed "s/^/[$s] /"
> done
[tank/app@auto-2025-12-25_0000] cannot destroy snapshot tank/app@auto-2025-12-25_0000: snapshot has holds
[tank/app@auto-2025-12-25_0030] destroyed
[tank/app@auto-2025-12-25_0100] destroyed
[tank/app@auto-2025-12-25_0130] destroyed
[tank/app@auto-2025-12-25_0200] destroyed

Interpretación: Los scripts de limpieza deben tratar “has holds” como un estado esperado. Regístralo, repórtalo, sigue adelante. No falles todo el trabajo ni, desde luego, intentes “arreglarlo” automáticamente.

Tarea 14: Encontrar snapshots con holds y ordenar por tiempo de creación

cr0x@server:~$ sudo zfs holds -r tank | awk 'NR==1{next} {print $1}' | sort -u | while read snap; do
>   sudo zfs get -H -o value creation "$snap" | awk -v s="$snap" '{print $0 " " s}'
> done | sort | head
Fri Dec 20 00:00 2025 tank/projects/beta@auto-2025-12-20_0000
Fri Dec 25 02:00 2025 tank/projects@quarterly-freeze-2025Q4
Fri Dec 25 02:00 2025 tank/projects/alpha@quarterly-freeze-2025Q4

Interpretación: Esto ayuda a responder la pregunta de cumplimiento: “¿Qué hemos fijado y cuánto tiempo lleva fijado?” La herramienta no lo hace en una tabla ordenada, así que construyes una.

Tres micro-historias del mundo corporativo

1) El incidente causado por una suposición equivocada

El equipo tenía una regla sencilla: “Conservamos 48 horas de snapshots y todo lo más antiguo se elimina.” Se aplicaba con un cron job escrito años atrás. A nadie le encantaba, pero evitaba que el pool se convirtiera en un museo.

Luego migraron una carga ruidosa—artefactos de CI y caches de build—al mismo pool que unas pocas bases de datos críticas. El pool empezó a llenarse más rápido de lo esperado. Bajo presión, alguien ejecutó el job de limpieza manualmente, dos veces, y luego decidió “acelerarlo” ampliando el patrón de destroy. Se usaron comodines. Nada explotó de inmediato, y así es como los incidentes de almacenamiento te atraen a una falsa confianza.

Más tarde ese día, la replicación al sitio DR falló. No falló por un “glitch temporal”: falló por “base incremental no encontrada”. La suposición era que si una snapshot tenía más de 48 horas, era seguro borrarla. Pero el calendario de replicación se había desviado durante una ventana de mantenimiento y ahora llevaba días de retraso. La cadena incremental dependía de una snapshot que el job de limpieza consideró “expirada”.

Acabaron realizando un reenvío completo a través de un enlace dimensionado para incrementales, no para datasets completos. El impacto en el negocio no fue solo el retraso en la postura DR. El reenvío compitió con el tráfico de producción y empujó la latencia a niveles visibles por los usuarios.

La corrección del postmortem no fue “decir a la gente que no use comodines” (buena suerte). Fue operativamente aplicable: el job de replicación aplicó una etiqueta de hold a la última snapshot confirmada en el destino. El job de retención destruyó libremente, pero se saltó las snapshots fijadas. La suposición equivocada se reemplazó por un mecanismo que hizo que el comportamiento seguro fuera por defecto.

2) La optimización que salió mal

En otra empresa tuvieron una idea ingeniosa: “Retengamos todas las snapshots durante siete días y luego liberemos en un lote. Así solo pensamos en retención una vez por semana.” Parecía eficiente: menos piezas móviles, menos oportunidades de equivocarse.

Funcionó—hasta que la carga cambió. Una nueva canalización de analítica empezó a reescribir grandes datasets a diario. Las snapshots empezaron a acumular grandes deltas. Los holds fijaron esos deltas durante una semana completa, y el espacio libre del pool empezó a oscilar. Cada semana, liberaban holds y hacían una destrucción masiva, obteniendo un breve respiro de espacio libre… seguido de presión de asignación similar a la fragmentación y caídas de rendimiento cuando el pool luchaba por reescribir bloques calientes mientras también liberaba los antiguos.

La optimización simplificó la retención, pero concentró la amplificación de escritura y el trabajo de eliminación en una tormenta semanal predecible. Peor aún, cuando la tormenta coincidía con un periodo de alta actividad, los picos de latencia se volvieron regulares. No era un misterio; era dolor programado.

La solución fue aburrida: retención escalonada. Sigue usándose holds, pero solo para las snapshots que realmente necesitan protección (bases de replicación, puntos previos a cambios y cumplimiento). Todo lo demás siguió una ventana rodante con eliminaciones graduales. También empezaron a vigilar el espacio libre del pool de forma más conservadora, porque los holds convierten “espacio libre” en una promesa que no siempre puedes cobrar hoy.

La lección: los holds son un mecanismo de seguridad, no una política única. Si optimizas para menos decisiones, puedes optimizar accidentalmente para caos periódico.

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

Esta es mi favorita porque no implica heroísmo, solo disciplina.

Una aplicación financiera tenía procedimientos de cierre trimestral que siempre eran de alta tensión. El equipo de almacenamiento tenía un paso en el runbook: antes de comenzar el cierre, tomar una snapshot recursiva de un subtree específico y aplicar una etiqueta legal de hold. Todos ponían los ojos en blanco porque tomaba cinco minutos extra y nunca parecía “hacer” nada.

Durante un cierre, una herramienta de despliegue leyó mal un valor de configuración y ejecutó un paso de limpieza contra el mountpoint equivocado. No fue malicioso; fue el tipo de bug de automatización que solo aparece cuando una variable está vacía y alguien no la cita. Los archivos desaparecieron. El equipo de la aplicación intentó revertir a nivel de app, pero la eliminación ya había ocurrido en disco.

Fueron a las snapshots, como estaba planeado—excepto que el primer intento de restauración falló porque el ingeniero on-call (privado de sueño, pero bienintencionado) empezó a eliminar “snapshots más antiguas” para crear espacio para la restauración. El pool estaba justo. Aquí es donde las organizaciones normales pierden el hilo: acabas destruyendo las mismas snapshots que necesitas porque el sistema te presiona a actuar.

Las snapshots trimestrales estaban en hold. No podían destruirse en el calor del momento. Eso obligó al equipo a elegir un camino más seguro: expandir temporalmente la capacidad y restaurar desde el conjunto de snapshots fijadas. El incidente aún dolió, pero se mantuvo en la categoría de “caro y molesto”, no “irreversible”.

La práctica no era glamurosa. Era un paso mecánico que impidió que una persona en pánico cometiera un error permanente.

Guía de diagnóstico rápido

Esta es la guía para el momento común en producción: “Necesitamos espacio, intentamos borrar snapshots, ZFS se niega y el rendimiento se vuelve raro.” El objetivo es encontrar el cuello de botella y el punto de decisión rápidamente, no admirar el sistema de archivos.

1) Primera comprobación: ¿qué exactamente está bloqueando el destroy?

Elige una snapshot específica que se niega a desaparecer e inspecciona los holds.

cr0x@server:~$ sudo zfs destroy tank/app@auto-2025-12-24_0000
cannot destroy snapshot tank/app@auto-2025-12-24_0000: snapshot has holds

cr0x@server:~$ sudo zfs holds tank/app@auto-2025-12-24_0000
NAME                               TAG                  TIMESTAMP
tank/app@auto-2025-12-24_0000       replication:last-good Thu Dec 24 00:05 2025

Decisión: Si la etiqueta es esperada (replicación/cumplimiento), no la elimines bajo presión sin confirmar la dependencia descendente.

2) Segunda comprobación: ¿los holds están generalizados o aislados?

Inventaría holds recursivamente para ver si tratas con unas pocas snapshots fijadas o con todo un subtree.

cr0x@server:~$ sudo zfs holds -r tank/app | wc -l
128

Interpretación: ¿Número grande? Probablemente tengas un problema de política o automatización, no un caso puntual.

3) Tercera comprobación: ¿dónde se está yendo realmente el espacio?

Mira el uso de espacio de datasets y snapshots, enfocándote en lo que está fijado.

cr0x@server:~$ sudo zfs list -o name,used,avail,refer,compressratio,mounted tank
NAME   USED  AVAIL  REFER  RATIO  MOUNTED
tank   68T   1.2T   256K   1.42x  yes

cr0x@server:~$ sudo zfs list -t snapshot -o name,used -s used -r tank/app | tail -n 5
tank/app@auto-2025-12-23_0000   210G
tank/app@auto-2025-12-24_0000   260G
tank/app@auto-2025-12-25_0000   300G
tank/app@quarterly-freeze       420G
tank/app@legal-freeze           1.1T

Interpretación: Las snapshots con gran USED son sospechosas principales. Si están en hold, no puedes recuperar ese espacio rápidamente sin cambiar la decisión de negocio.

4) Cuarta comprobación: ¿estás golpeando un cuello de botella de rendimiento a nivel de pool?

Problemas de espacio e intentos de eliminación pueden coincidir con latencias altas. Revisa salud del pool e I/O rápidamente.

cr0x@server:~$ sudo zpool status -x
all pools are healthy

cr0x@server:~$ iostat -xz 1 3
Linux 6.8.0 (server)  12/25/2025  _x86_64_  (32 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.40    0.00    4.10    8.20    0.00   75.30

Device            r/s     w/s   rkB/s   wkB/s  await  %util
nvme0n1         220.0   180.0  18000   26000   6.20  92.0
nvme1n1         210.0   170.0  17500   24000   6.50  89.0

Interpretación: %util alto y await en aumento sugiere que estás limitado por I/O. Las eliminaciones de snapshots pueden no ser la causa, pero la misma carga de escritura que creó grandes deltas a menudo sí lo es.

5) Quinta comprobación: confirma que no estás bloqueado por un send/receive o scrub de larga duración

cr0x@server:~$ sudo zpool status tank | sed -n '1,25p'
  pool: tank
 state: ONLINE
  scan: scrub repaired 0B in 02:10:21 with 0 errors on Fri Dec 25 01:30:11 2025
config:

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          mirror-0  ONLINE       0     0     0
            nvme0n1 ONLINE       0     0     0
            nvme1n1 ONLINE       0     0     0

Interpretación: Si un scrub/resilver está activo, el pool estará ocupado. No bloquea directamente los destroys, pero cambia la evaluación de riesgo cuando además estás corto de espacio.

Errores comunes: síntomas y correcciones

Error 1: Tratar “snapshot has holds” como un fallo a remediar automáticamente

Síntoma: El job de limpieza falla cada noche, reintenta agresivamente, alerta a alguien o (peor) libera holds automáticamente para “arreglarlo”.

Corrección: Haz de “has holds” un estado de primera clase: registra y reporta las snapshots fijadas por separado. Liberar holds debe requerir comprobaciones explícitas de propiedad (éxito de replicación, cierre de la ventana de cambio, aprobación legal).

Error 2: Usar etiquetas vagas que no identifican un propietario

Síntoma: Encuentras etiquetas como keep, hold o important. Nadie puede eliminarlas con seguridad; perduran años.

Corrección: Adopta una convención de nombres: system:purpose (opcionalmente con un ID de cambio). Ejemplo: replication:last-good, change:pre-upgrade, legal:q4-retention.

Error 3: Fijar snapshots “por si acaso” sin medir impacto en espacio

Síntoma: El espacio libre del pool tiende a bajar constantemente y eliminar snapshots no fijadas apenas mueve la aguja.

Corrección: Identifica las snapshots fijadas con alto USED. Decide si realmente son necesarias. Si el cumplimiento requiere retención, planifica capacidad en consecuencia; no finjas que puedes limpiar para salir del apuro.

Error 4: Holds recursivos aplicados al alcance equivocado

Síntoma: De repente miles de snapshots en muchos child datasets están fijadas; el consumo de almacenamiento se dispara; la retención deja de funcionar.

Corrección: Antes de usar -r, lista el árbol de datasets y confirma la raíz prevista. En automatización, pon en lista blanca prefijos de datasets. Considera fijar solo nombres de snapshot específicos en lugar de aplicar a todos los hijos automáticamente.

Error 5: Confundir holds con permisos y control de acceso

Síntoma: La gente asume que los holds impiden a usuarios privilegiados realizar acciones destructivas en todo el pool.

Corrección: Usa permisos delegados de ZFS y controles operativos. Los holds protegen snapshots de la destrucción, no protegen todo tu almacenamiento contra root.

Error 6: Scripts de replicación que ponen holds pero nunca los liberan

Síntoma: replication:last-good existe en docenas o cientos de snapshots; solo querías una.

Corrección: Tras una replicación exitosa, rota el hold: aplícalo a la nueva base, libéralo de la base anterior y luego elimina las bases antiguas si la política lo permite. Añade monitorización que alerte si más de N snapshots están etiquetadas replication:last-good.

Error 7: Suponer que los holds son la única razón por la que una snapshot no puede destruirse

Síntoma: Destroy falla y la gente se fija solo en los holds, pero el problema está en otra parte (error tipográfico, clones dependientes, problemas de permisos).

Corrección: Lee el mensaje de error completo y también comprueba clones y permisos. Los holds son comunes, pero no exclusivos.

Listas de verificación / plan paso a paso

Checklist A: Introducir holds de forma segura en un entorno existente

  1. Define etiquetas de propiedad. Decide qué sistemas pueden colocar holds (replicación, backup, gestión de cambios, legal).
  2. Elige una convención de nombres. Prefiere owner:purpose y opcionalmente añade un identificador corto en el nombre de la snapshot.
  3. Decide reglas de alcance. ¿Cuándo están permitidos los holds recursivos? ¿En qué raíces de dataset? Documenta los prefijos permitidos.
  4. Actualiza los jobs de limpieza. Trata “has holds” como una condición de salto. Genera un informe separado de snapshots fijadas.
  5. Añade visibilidad. Crea un job programado que cuente holds por etiqueta y liste la snapshot fijada más antigua por etiqueta.
  6. Haz un simulacro. Practica el procedimiento de “emergencia por falta de espacio” sin estar realmente corto de espacio, para no aprender bajo estrés.

Checklist B: Snapshot de seguridad previa a un cambio usando holds

  1. Toma snapshot(s) de los dataset(s) involucrados, idealmente recursiva si el cambio abarca varios child datasets.
  2. Aplica una etiqueta de hold específica del cambio.
  3. Verifica que existan los holds.
  4. Procede con el cambio.
  5. Tras la ventana de validación, libera la etiqueta de hold.
  6. Permite que la retención normal elimine la snapshot más tarde (o destrúyela explícitamente si la política lo requiere).
cr0x@server:~$ sudo zfs snapshot -r tank/app@chg-pre-2025-12-25_0500
cr0x@server:~$ sudo zfs hold -r change:chg-pre tank/app@chg-pre-2025-12-25_0500
cr0x@server:~$ sudo zfs holds -r tank/app@chg-pre-2025-12-25_0500 | head -n 5
NAME                                     TAG              TIMESTAMP
tank/app@chg-pre-2025-12-25_0500         change:chg-pre   Fri Dec 25 05:00 2025
tank/app/db@chg-pre-2025-12-25_0500      change:chg-pre   Fri Dec 25 05:00 2025
tank/app/uploads@chg-pre-2025-12-25_0500 change:chg-pre   Fri Dec 25 05:00 2025

Checklist C: Retención de replicación con holds “última buena base”

  1. Crea una nueva snapshot de replicación.
  2. Envía/recibe (o el mecanismo de replicación que uses).
  3. Verifica el éxito en el lado destino.
  4. Fija la nueva snapshot base en el origen.
  5. Libera el hold en la base anterior.
  6. Opcionalmente destruye bases antiguas si están fuera de retención.

Preguntas frecuentes

1) ¿Un hold es lo mismo que poner una propiedad de dataset como readonly=on?

No. readonly=on afecta el comportamiento de escritura en un dataset. Un hold afecta solo si una snapshot específica puede ser destruida.

2) ¿Puedo fijar un dataset, no una snapshot?

Los holds se aplican a snapshots. Puedes snapshotear de forma recursiva y luego fijar esas snapshots, que es a menudo lo que la gente quiere decir operacionalmente con “fijar un árbol de datasets”.

3) ¿Por qué ZFS permite múltiples holds en una snapshot?

Porque en entornos reales hay múltiples interesados. La replicación puede necesitar una snapshot para cadenas incrementales, mientras que la retención legal puede necesitarla por gobernanza. Múltiples holds permiten que cada sistema afirme su requisito independientemente.

4) Si una snapshot está fijada, ¿sigue consumiendo más espacio con el tiempo?

La snapshot en sí no cambia, pero puede mantener bloques antiguos referenciados mientras el dataset en vivo cambia. Cuanta más actividad tenga el dataset, más bloques únicos antiguos quedan fijados por esa snapshot.

5) ¿Cómo encuentro qué impide la eliminación de una snapshot?

Ejecuta zfs holds <snapshot>. Si tratas con muchos datasets, usa zfs holds -r para inventariar y luego afina a las snapshots específicas que bloquean tu política.

6) ¿Puedo usar holds como mecanismo de retención por cumplimiento?

Puedes usar los holds para imponer “no se puede borrar hasta que se libere explícitamente”, que es un primitivo útil. Si satisface cumplimiento depende de tus controles sobre quién puede liberar holds, la auditabilidad y si usuarios privilegiados pueden eludir tu proceso previsto.

7) ¿Cuál es la diferencia entre un hold y un bookmark?

Un hold fija una snapshot (y por tanto todos los bloques referenciados). Un bookmark preserva un punto para envío incremental sin retener los bloques de la snapshot completa de la misma manera. Los holds son sobre indelebilidad; los bookmarks son sobre la línea de replicación sin retener la snapshot completa.

8) Mi herramienta de retención elimina snapshots pero no recupera espacio. ¿Son los holds los culpables?

Posiblemente, pero no siempre. Los holds impiden la eliminación de snapshots específicas; no explican directamente por qué la eliminación de otras no libera mucho. Causas comunes: las snapshots restantes (fijadas o no) todavía referencian los bloques antiguos, el patrón de churn del dataset mantiene bloques referenciados por snapshots más nuevas, o estás mirando la jerarquía de dataset incorrecta.

9) ¿Debería poner holds en cada snapshot?

Normalmente no. Así conviertes una política de retención en una crisis de capacidad. Usa holds selectivamente: bases de replicación de última buena, puntos de seguridad previos a cambios y snapshots de cumplimiento explícito.

10) ¿Quién debería poder liberar holds?

En entornos maduros: solo la automatización propietaria o un pequeño conjunto de roles privilegiados con registro de auditoría. Operativamente, “liberar un hold” equivale a “permitir eliminación permanente”, y debe tratarse con la misma seriedad.

Conclusión

zfs hold es una característica pequeña con consecuencias desproporcionadas. No hace que las snapshots sean “más snapshotadas”. Las hace más difíciles de destruir por accidente, y eso es un superpoder sorprendentemente raro en sistemas de producción—donde la mayoría de los fallos no son bugs exóticos del kernel sino humanos ordinarios moviéndose rápido.

Si adoptas holds con clara propiedad de etiquetas, los integras en la retención y replicación, y construyes el hábito de auditar lo que está fijado, obtendrás un sistema que falla de forma más segura bajo estrés. Y cuando llegue el día en que alguien intente borrar lo equivocado para apagar la alerta roja, ZFS hará lo que le enseñaste: negarse.

← Anterior
GPUs con chiplets: por qué la idea tiene lógica — y es brutalmente difícil
Siguiente →
Proxmox HA “no se puede iniciar el recurso»: localizar el bloqueo real (quórum, almacenamiento, red)

Deja un comentario