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:
- Las snapshots de ZFS no son copias. Son un conjunto de punteros a bloques; los “datos antiguos” permanecen referenciados hasta que nada los apunta.
- 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.
- 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.
- 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.
- 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.
- 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í.
- 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.
- “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
- Define etiquetas de propiedad. Decide qué sistemas pueden colocar holds (replicación, backup, gestión de cambios, legal).
- Elige una convención de nombres. Prefiere
owner:purposey opcionalmente añade un identificador corto en el nombre de la snapshot. - Decide reglas de alcance. ¿Cuándo están permitidos los holds recursivos? ¿En qué raíces de dataset? Documenta los prefijos permitidos.
- Actualiza los jobs de limpieza. Trata “has holds” como una condición de salto. Genera un informe separado de snapshots fijadas.
- Añade visibilidad. Crea un job programado que cuente holds por etiqueta y liste la snapshot fijada más antigua por etiqueta.
- 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
- Toma snapshot(s) de los dataset(s) involucrados, idealmente recursiva si el cambio abarca varios child datasets.
- Aplica una etiqueta de hold específica del cambio.
- Verifica que existan los holds.
- Procede con el cambio.
- Tras la ventana de validación, libera la etiqueta de hold.
- 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”
- Crea una nueva snapshot de replicación.
- Envía/recibe (o el mecanismo de replicación que uses).
- Verifica el éxito en el lado destino.
- Fija la nueva snapshot base en el origen.
- Libera el hold en la base anterior.
- 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.