
🌐 También en: English · Deutsch · Français
Ya he escrito dos veces sobre este NAS: primero sobre cómo montarlo con RAIDZ1 en Debian 13 y después el anexo sobre cómo se programan realmente el scrub y el trim, y sobre la alerta que nunca llegó.
Esta vez toca hablar de versionado. Durante años, mi respuesta a «¿cómo recupero la versión de ayer de este archivo?» ha sido rdiff-backup, y es una respuesta realmente buena. De hecho, sigue instalado en esta máquina:
$ rdiff-backup --version
rdiff-backup 2.2.6Pero montar el pool sobre OpenZFS puso un segundo mecanismo sobre la mesa, uno que resuelve el mismo problema desde una dirección completamente distinta. Así que dediqué una tarde a entender cómo funcionan de verdad los snapshots de ZFS, cuánto cuestan y en qué superan a la herramienta que llevaba tiempo usando tan contento. Este artículo es esa tarde, incluidas las partes que hice mal al primer intento.
🔄 Dos formas de responder a la misma pregunta
Ambas herramientas responden a «dame el estado de hace N días». Solo discrepan radicalmente sobre cuándo se hace el trabajo.
rdiff-backup mantiene un espejo simple del estado más reciente, más incrementos inversos en rdiff-backup-data/. Para hacer una copia de seguridad recorre todo el árbol de origen, lo compara con el espejo y calcula con librsync deltas binarios de todo lo que haya cambiado. El trabajo ocurre en el momento de la copia y crece con la cantidad de datos que tienes, no con la cantidad que ha cambiado. Restaurar una versión antigua significa partir del espejo y aplicar los incrementos hacia atrás.
Los snapshots de ZFS no hacen nada de eso, porque el sistema de archivos ya lo sabe. ZFS es copy-on-write: cuando modificas un bloque, nunca lo sobrescribe en su sitio; escribe la nueva versión en otra parte y cambia el puntero. Normalmente, el bloque antiguo pasaría a ser espacio libre.
Un snapshot dice exactamente una cosa: conserva todos los bloques a los que apunta ahora mismo este dataset.
No hay nada que escanear, nada que comparar, nada que calcular. Por eso crear uno tiene este aspecto:
$ zfs list -t snapshot -o name,used,creation
NAME USED CREATION
storage/user1@auto-2026-09-11_1250 0B Fr Sep 11 12:50 2026
storage/user2@auto-2026-09-11_1250 0B Fr Sep 11 12:50 2026
storage/nextcloud@auto-2026-09-11_1250 0B Fr Sep 11 12:50 2026
storage/user3@auto-2026-09-11_1250 0B Fr Sep 11 12:50 20260B. Cuatro snapshots que cubren 1,8 TB, creados en milisegundos, sin ocupar nada. Todo el mundo sonrió, se hizo la foto y el carrete no costó nada, porque no hay carrete. Un snapshot no es una copia de tus datos. Es la promesa de no tirar la versión actual.
⚡ ¿De verdad es más rápido? Sí, pero hay que precisar en qué
Crear un snapshot es O(1). Tarda los mismos pocos milisegundos tanto si el dataset contiene 14 GB como 1,17 TB, porque el coste no depende en absoluto de los datos, solo de congelar un conjunto de referencias.
rdiff-backup tiene que recorrer el árbol en cada ejecución para descubrir qué ha cambiado. En storage/user3 (1,17 TB) eso supone una cantidad considerable de E/S y de tiempo, incluso una noche en la que no se ha movido nada.
La restauración es la misma historia al revés. Cada snapshot de ZFS es un árbol completo y legible directamente: sin paso de reconstrucción, sin aplicar incrementos, sin esperas:
$ ls /storage/nextcloud/.zfs/snapshot/auto-2026-09-11_1250/
instance-a instance-bEse es el estado de aquel momento, explorable ahora mismo con ls, cp, rsync o un gestor de archivos.
La advertencia honesta, porque aquí es donde la comparación suele venderse de más: un snapshot solo versiona datos que ya están en el pool. No mueve nada al NAS. Llevar los datos de un servidor a esta máquina sigue siendo trabajo de rsync: el snapshot sustituye la capa de versionado, no la transferencia. Y rdiff-backup puede escribir en una máquina completamente distinta por SSH, algo que los snapshots no pueden hacer de ninguna manera. Más sobre eso abajo.
💾 ¿Y el espacio?
Ambos enfoques son eficientes, pero de forma distinta, y la respuesta honesta lleva un «depende».
rdiff-backup guarda deltas binarios comprimidos con granularidad de byte. Cambia tres filas de un volcado de base de datos y el incremento es minúsculo: realmente impresionante para ese patrón.
ZFS conserva records modificados completos. En este pool son 128 KB:
$ zfs get recordsize,compression,compressratio storage/nextcloud
NAME PROPERTY VALUE
storage/nextcloud recordsize 128K
storage/nextcloud compression lz4
storage/nextcloud compressratio 1.03xAsí que un cambio de un byte en un archivo enorme retiene un bloque de 128 KB, donde rdiff-backup quizá habría guardado unos cientos de bytes. Más grueso, sin discusión.
Lo que ZFS ofrece a cambio:
- Sin sobrecoste de espejo. El estado más reciente de rdiff-backup es una segunda copia completa de todo. Los snapshots comparten cada bloque sin cambios con los datos vivos: no hay ninguna base duplicada.
- Sin contabilidad por versión. Treinta snapshots de un dataset sin cambios cuestan treinta veces nada.
- lz4 en todas partes, de forma transparente, tanto en los datos vivos como en los snapshots.
Ese compressratio de 1,03x merece un comentario honesto: mis datos son fotos y contenido multimedia, ya comprimidos, así que lz4 aquí no me aporta casi nada. No es una crítica a lz4: es el resultado correcto para este contenido, y sería muy distinto en un dataset lleno de logs o de texto.
Resultado neto para datos mixtos típicos de casa y oficina: comparable en espacio, muchísimo más barato en operación. Para el patrón concreto de cambios pequeños y frecuentes dentro de archivos muy grandes, los deltas a nivel de byte de rdiff-backup siguen ganando en bytes.
📁 Dónde viven los snapshots: el directorio que no está
Los snapshots no son archivos que puedas abrir con un gestor de archivos. Pero ZFS te da una ventana que se comporta como un directorio normal y corriente:
$ ls /storage/nextcloud/.zfs/snapshot/
auto-2026-09-11_1250La trampa en la que todo el mundo cae exactamente una vez: .zfs está oculto por defecto y no aparece en ls -la. No es una entrada de directorio normal, es sintética. Escribe la ruta de todos modos y funciona.
$ zfs get snapdir storage/nextcloud
NAME PROPERTY VALUE SOURCE
storage/nextcloud snapdir hidden defaultTodo lo que hay ahí dentro es de solo lectura. No puedes dañar un snapshot por explorarlo ni escribir en él por accidente. Solo puedes copiar hacia fuera, que es justo lo que quieres durante una recuperación.
⏱️ El script, y por qué esta vez temporizadores de systemd
En el artículo sobre scrub y trim descubrí que Debian programa el mantenimiento de ZFS mediante /etc/cron.d/zfsutils-linux, con los temporizadores de systemd incluidos pero desactivados. Para los snapshots tomé deliberadamente el camino contrario:
[Timer]
OnCalendar=daily
RandomizedDelaySec=15m
Persistent=truePersistent=true es el motivo. Si el NAS está apagado a medianoche, cron simplemente se salta ese día y no avisa a nadie. systemd ejecuta la tarea perdida en el siguiente arranque. Para algo cuyo único valor es existir antes de que lo necesites, saltarse días en silencio no es un modo de fallo aceptable, y yo acababa de dedicar un artículo entero a cosas que fallan en silencio.
RandomizedDelaySec escalona los cuatro datasets para que no se disparen todos en el mismo segundo.
El script de rotación hace una sola cosa: crear un snapshot y luego recortar los más antiguos hasta el número de retención.
#!/bin/bash
# /usr/local/sbin/zfs-snapshot-rotate.sh
set -euo pipefail
DATASET="${1:?Dataset missing}"
KEEP="${2:-30}"
PREFIX="auto-"
zfs list -H -o name "$DATASET" >/dev/null 2>&1 || {
echo "Dataset $DATASET does not exist" >&2; exit 1; }
zfs snapshot "${DATASET}@${PREFIX}$(date +%Y-%m-%d_%H%M)"
# Newest first, keep the first $KEEP, everything after that goes.
# "|| true": grep returns 1 when nothing matches yet, which is not an error.
mapfile -t old < <(
zfs list -H -t snapshot -o name -S creation -r "$DATASET" 2>/dev/null \
| grep -E "^${DATASET}@${PREFIX}" \
| tail -n "+$((KEEP + 1))" || true
)
for snap in "${old[@]}"; do
[ -n "$snap" ] || continue
[[ "$snap" == "${DATASET}@${PREFIX}"* ]] || continue
[[ "$snap" == *"@"* ]] || continue
zfs destroy "$snap"
doneTres comprobaciones antes de destruir nada, y no es teatro paranoico. zfs destroy acepta un nombre de snapshot y un nombre de dataset exactamente en la misma posición de argumento. Si $snap llegara a estar vacío o truncado, zfs destroy storage/user3 se llevaría por delante 1,17 TB de fotos familiares sin hacer ni una pregunta. La comprobación de la @ lo hace estructuralmente imposible.
El filtro de prefijo también importa: solo los snapshots creados por este script pueden borrarse. Cualquier cosa creada a mano, o por otra herramienta, es invisible para él.
🧪 Probar la ruta de borrado, porque claro que se prueba la ruta de borrado
Un zfs destroy sin probar dentro de un temporizador nocturno que apunta a tus propios datos no es algo que quieras descubrir en mal momento. Así que, antes de fiarme, ejecuté la rotación contra el dataset más pequeño con KEEP=1, obligándola a borrar algo de verdad:
--- before ---
storage/user1@auto-2026-09-11_1252
--- rotate with keep=1 ---
--- after ---
storage/user1@auto-2026-09-11_1253
--- dataset intact? ---
storage/user1 14.4GEl snapshot antiguo, fuera; el nuevo, conservado; el dataset, intacto. Verificado en lugar de supuesto.
Una limitación con la que me topé en las pruebas y que decidí mantener: los nombres llevan %H%M, así que dos ejecuciones en el mismo minuto chocan con «dataset already exists». Irrelevante para un temporizador diario, un acertijo de dos segundos para quien pruebe a mano. Ahora ya lo sabes.
🦠 El ángulo del ransomware, que es donde esto realmente compensa
Varias personas sincronizan con mi Nextcloud mediante el cliente de escritorio de Windows, el que monta la nube como una unidad en el Explorador. Es cómodo, y también significa que la descripción del puesto del cliente es «detectar archivos modificados y subirlos». Un ransomware en un equipo así cifra los archivos locales, y el cliente hace obedientemente aquello para lo que fue creado.
Este es el escenario en el que la diferencia entre un espejo y un historial versionado deja de ser académica. Un espejo puro reproduce fielmente el estado cifrado. Un historial versionado te permite retroceder más allá.
El orden de recuperación que funciona de verdad:
Primero, detén la sincronización. Desconecta el cliente afectado o pon Nextcloud en modo mantenimiento. Restaurar dentro de una sincronización activa con un equipo infectado solo consigue que tu restauración vuelva a cifrarse, y encima habrás perdido el tiempo.
Después, prueba las capas propias de Nextcloud. La papelera del servidor y el versionado de archivos son, con diferencia, el camino más barato, y para cualquier cosa que no sea un incidente masivo suelen bastar por sí solos.
Luego recurre a los snapshots. Y aquí importa exactamente una distinción:
zfs rollbackrestablece el dataset en su sitio y destruye de forma irreversible todo lo más reciente, incluidos los snapshots más recientes. Si te equivocas con el momento, tu segundo intento ha desaparecido.- Copiar desde
.zfs/snapshot/<name>/no cambia absolutamente nada. Hazlo diez veces, compara resultados, tómate tu tiempo.
Usa la segunda opción. El rollback es una herramienta afilada para otro trabajo.
Tampoco tienes que adivinar cuándo empezó el daño, que fue mi primer impulso. ZFS te lo dice:
$ zfs diff storage/nextcloud@auto-2026-09-10_0000 \
storage/nextcloud@auto-2026-09-11_0000El snapshot en el que de repente miles de archivos aparecen como modificados es el de después del incidente. Coge el anterior.
📤 Donde rdiff-backup sigue ganando, y no es poca cosa
Los snapshots viven en el pool. Los mismos tres discos, el mismo destino. RAIDZ1 sobrevive a un disco muerto; no sobrevive a un incendio, a un robo, a una controladora que se lleva el array por delante ni a alguien con root que ejecute zfs destroy.
Así que los snapshots protegen frente a daños lógicos: ransomware, un borrado por un dedazo, una sincronización que salió mal. No hacen nada contra la pérdida física. Esa es la mitad que rdiff-backup siempre ha cubierto bien, escribiendo por SSH en hardware completamente separado.
ZFS tiene su propia respuesta aquí, y encaja muy bien:
$ zfs send storage/nextcloud@auto-2026-09-11_1250 > /mnt/usb/nextcloud.zfs
$ zfs send storage/nextcloud@auto-2026-09-11_1250 | zfs recv usbpool/nextcloudA partir de ahí, zfs send -i solo envía el delta entre dos snapshots, lo que convierte el «disco externo que vive en un cajón» en algo práctico en vez de un proyecto de fin de semana.
La conclusión a la que llegué no es «sustituir rdiff-backup». Es que estas herramientas nunca compitieron de verdad:
- Snapshots: historial instantáneo, gratuito e ilimitado frente a daños lógicos, justo donde viven los datos
- Una copia en hardware separado: lo único que sobrevive a perder la propia máquina
🔧 Los comandos que de verdad vale la pena recordar
Inspección:
$ zfs list -t snapshot -o name,used,creation # what exists, how big, when
$ zfs list -o name,used,avail -r storage # datasets and space
$ systemctl list-timers 'zfs-snapshot@*' # when did/will it run
$ journalctl -u 'zfs-snapshot@*' --since today # did it workExplorar y recuperar:
$ ls /storage/<dataset>/.zfs/snapshot/ # available points in time
$ ls /storage/<dataset>/.zfs/snapshot/auto-*/ # the files themselves
$ zfs diff <snapshot-a> <snapshot-b> # what changed between them
$ rsync -a /storage/x/.zfs/snapshot/<snap>/foo/ /restore/foo/Operaciones manuales:
$ zfs snapshot storage/user3@before-something-risky
$ zfs destroy storage/user3@before-something-risky
$ zfs hold keep storage/user3@important # protect from deletionzfs hold está infravalorado. Hace que un snapshot se niegue a ser destruido hasta que lo liberes explícitamente: ideal para ese estado bueno conocido que quieres que sobreviva a cualquier lógica de rotación, incluida la tuya.
⚠️ Dos cosas que debes saber antes de confiar en esto
La retención es un límite real. Aquí, treinta días. Un incidente que pase desapercibido más tiempo queda fuera de alcance por la rotación. Cómodo para un sistema en uso activo; para archivos fríos, añade un nivel mensual en lugar de cruzar los dedos.
Los snapshots siguen a los datasets, no a los directorios. Esta me pilló revisando mi propio trabajo. /storage contiene cuatro datasets y un directorio normal que no es un dataset y que, por tanto, se queda calladamente sin nada:
$ zfs list /storage/gitlab-migration
NAME USED AVAIL REFER MOUNTPOINT
storage 1.78T 1.73T 160K /storageSe resolvió al padre, no a sí mismo. Haz mañana un mkdir /storage/something y no heredará ninguna política de snapshots, sin el menor aviso. Usa en su lugar zfs create storage/something y tendrá todo lo suyo: snapshots, cuotas, propiedades. No cuesta nada, y es la diferencia entre la protección que tienes y la que das por hecha.
La verdadera conclusión
Lo que más me llamó la atención al hacer todo esto no fue la eficiencia en espacio, ni siquiera la velocidad. Fue dónde se hace el trabajo.
rdiff-backup, como casi todas las herramientas de copia de seguridad, tiene que ir a averiguar qué ha cambiado: recorrer árboles, comparar, calcular. Es un esfuerzo enorme para redescubrir algo que el sistema de archivos vio ocurrir en tiempo real y luego olvidó.
ZFS no olvida. Copy-on-write significa que la información ya estaba ahí; un snapshot simplemente se niega a descartarla. Por eso es instantáneo, y por eso no cuesta nada hasta que algo cambia de verdad.
Un buen recordatorio de que la pregunta interesante sobre infraestructura no suele ser «¿qué herramienta es mejor?», sino «¿quién conoce ya la respuesta, y se la estoy preguntando… o la recalculo cada noche a las 3 de la madrugada?».




