ZFS Send incremental: copias de seguridad que no vuelven a copiarlo todo

¿Te fue útil?

La primera vez que ves un send incremental de ZFS terminar en segundos—después de que la copia completa de anoche tardara horas—experimentas esa rara sensación en infraestructura:
algo que es a la vez elegante y práctico. Las instantáneas de ZFS te dan puntos-in-time inmutables, y zfs send/zfs receive pueden replicar
solo lo que cambió entre esas instantáneas. El beneficio es obvio: copias más rápidas, menos tráfico de red y una historia de recuperación que no empieza con «lo siento, todavía estamos copiando».

Pero la replicación incremental no es magia. Es un contrato: tu cadena de instantáneas, las propiedades del dataset y el estado del receptor deben coincidir.
Cuando no lo hacen, ZFS se negará a adivinar. Eso es buena ingeniería, y ocasionalmente un mal día para quien asumió que «incremental» significa «simplemente funcionará».

Qué significa realmente «send incremental»

Un «stream de envío» de ZFS es una representación serializada de un dataset (o instantánea) que puede reconstruirse en otro pool con zfs receive.
Un envío completo transmite toda la instantánea. Un envío incremental transmite solo los bloques que cambiaron entre dos instantáneas—»de A a B».

Los envíos incrementales no son diffs fichero por fichero. ZFS trabaja a nivel de bloques. Si un archivo cambia de manera que reescribe bloques, esos bloques se envían.
Si no cambió nada, no se envía nada. Si un archivo se reescribe por completo (por ejemplo, una imagen de disco de VM con una pequeña edición que provoca patrones de reescritura grandes),
aún puedes enviar muchos datos de forma incremental. «Incremental» trata sobre bloques cambiados, no sobre intenciones cambiadas.

Hay dos modos incrementales principales que utilizarás:

  • Incremental de instantánea a instantánea: zfs send -i pool/fs@old pool/fs@new envía cambios desde @old hasta @new.
  • Incremental de replicación (a.k.a. «instantánea base»): zfs send -I pool/fs@base pool/fs@new puede incluir instantáneas intermedias en el stream.

Ese -I (i mayúscula) importa cuando quieres que el receptor termine con el mismo conjunto de instantáneas—no solo con la más reciente. En la mayoría de operaciones de copia,
el historial de instantáneas es parte del producto.

Broma #1: Las copias incrementales son como las dietas—si «haces trampa» eliminando la instantánea base, las cuentas dejan de cuadrar y lo notarás de inmediato.

Hechos y contexto interesantes (lo que cambia tus decisiones)

  1. Las instantáneas de ZFS son baratas porque son metadatos, no copias. El coste aparece más tarde cuando los bloques divergen y deben retenerse para instantáneas antiguas.
  2. Los streams de envío son determinísticos para un estado dado del dataset, pero no necesariamente estables entre características. Habilitar nuevas funciones de pool/dataset puede afectar la compatibilidad con receptores antiguos.
  3. OpenZFS unifica múltiples líneas de plataforma. El «ZFS» que ejecutas en Linux hoy es descendiente del Solaris ZFS, con años de desarrollo de funciones y endurecimiento operativo.
  4. Los tamaños de registro grandes pueden hacer que los incrementales sean sorprendentemente grandes. Si tu dataset usa recordsize=1M y una carga reescribe regiones pequeñas al azar, puedes churnear bloques grandes.
  5. Existe el envío reanudable porque las redes son poco fiables. Los tokens de reanudación te permiten continuar un receive tras una interrupción, en lugar de reiniciar un flujo de terabytes.
  6. La replicación ZFS puede preservar propiedades, ACLs y xattrs—pero solo si lo pides. Flags como -p y -x cambian qué se replica y qué se sobrescribe.
  7. Los bookmarks se introdujeron para mantener bases incrementales sin conservar instantáneas completas. Son referencias ligeras a un estado de instantánea, útiles en pipelines de replicación.
  8. La replicación cifrada tiene dos modos: raw y no-raw. La replicación «raw» mantiene el cifrado intacto y evita descifrar/recifrar en el emisor.
  9. zfs receive es deliberadamente estricto respecto al linaje. Si la última instantánea del receptor no coincide con la instantánea base del emisor, ZFS no hará un «mejor esfuerzo» con tus datos.

El modelo mental operativo: instantáneas, streams y linaje

Si recuerdas un concepto, que sea linaje. El send incremental funciona solo cuando el receptor tiene una instantánea que es byte-a-byte igual que la instantánea base del emisor.
Por eso las convenciones de nombres de instantáneas y las políticas de retención no son «agradables de tener»: son estructurales.

1) La cadena: base → cambios → nueva

Cuando ejecutas:

cr0x@server:~$ zfs send -i tank/app@2025-12-24_0000 tank/app@2025-12-25_0000 | zfs receive -u backup/app

ZFS calcula qué bloques son diferentes entre esas instantáneas y los transmite. En el receptor, esos bloques se aplican para reconstruir @2025-12-25_0000.
Si backup/app no tiene ya @2025-12-24_0000 con linaje GUID coincidente, el receive fallará.

2) Conjuntos de instantáneas vs «solo la última»

Un objetivo común de copia es: «conservar una semana de hourly y un mes de daily». Con ZFS, eso a menudo significa que quieres que el receptor también tenga esas instantáneas,
no solo el estado final. Ahí es donde -I ayuda: puede enviar un rango que incluya instantáneas intermedias, preservando el conjunto.

3) Propiedades, comportamiento de montaje y por qué tu servidor de backup montó datasets de producción

Los receives pueden crear datasets e instantáneas. Por defecto, un filesystem recibido puede montarse según propiedades heredadas. En un host de backup normalmente
quieres que los filesystems replicados permanezcan desmontados para evitar accesos accidentales, indexación o agentes «útiles» que los escaneen.

Por eso verás zfs receive -u en todos los playbooks sensatos: recibe el dataset pero no lo monta.

Diseñar un workflow de replicación que sobreviva en la práctica

Un diseño de replicación viable responde cuatro preguntas:

  • ¿Cuál es la unidad de replicación? ¿Un solo dataset, o un subárbol con hijos (p. ej., tank/services más todos los descendientes)?
  • ¿Cómo nombramos las instantáneas? Para que la automatización pueda calcular la «última instantánea común» y las personas puedan diagnosticar fallos rápidamente.
  • ¿Cómo manejamos interrupciones? Receives reanudables, timeouts y monitorización.
  • ¿Cuáles son nuestras invariantes? «Las copias están desmontadas», «replicamos propiedades», «no replicamos datasets temporales», y así sucesivamente.

Nomenclatura de instantáneas que los operadores puedan entender a las 03:00

Evita lo ingenioso. Usa timestamps ordenables y un prefijo estable. Por ejemplo:
auto-2025-12-25_0100, auto-2025-12-25_0200.
Si mantienes múltiples políticas, incluye el nombre de la política:
hourly-2025-12-25_0200, daily-2025-12-25.

Replicación cifrada raw: cuando las copias no deben ver texto plano

Si tus datasets están cifrados y quieres que tu host de backup almacene el texto cifrado (y no requiera claves), buscas envíos raw:
zfs send -w (o --raw, según tu plataforma). Esto mantiene la representación cifrada en disco intacta.
Es una gran ventaja operativa: tu sistema de backup puede tratarse como «almacenamiento», no como «cómputo confiable».

Replicación de hijos: un stream, muchos datasets

Usa -R (stream de replicación) cuando quieras replicar un dataset y sus descendientes, además de propiedades e instantáneas. Es la opción de «traer todo el árbol».
También es la opción que puede sorprenderte si no querías replicar ese dataset anidado que alguien creó para «pruebas temporales».

Ancho de banda y CPU: tu pipeline es un sistema

zfs send produce un stream; cómo lo transportas importa. El cifrado por SSH puede convertirse en tu cuello de botella antes que los discos.
La compresión puede ayudar si tus datos se comprimen y tu CPU es más barato que tu WAN. El buffering puede suavizar picos y mantener ocupados ambos extremos.

Broma #2: SSH es genial hasta que te das cuenta de que tu «appliance de backup» es en realidad un calefactor con puerto de red.

Tareas prácticas: comandos que realmente ejecutarás

Los comandos siguientes asumen dos hosts: un origen (prod) y un receptor de backup (backup).
Ajusta nombres de pool y dataset para que coincidan con tu entorno.

Tarea 1: Confirmar layout del dataset y propiedades básicas

cr0x@prod:~$ zfs list -o name,used,avail,refer,mountpoint,compression,recordsize -r tank/app
NAME              USED  AVAIL  REFER  MOUNTPOINT        COMPRESS  RECSIZE
tank/app          220G   1.4T   180G  /srv/app          lz4       128K
tank/app/db        40G   1.4T    40G  /srv/app/db       lz4       16K
tank/app/uploads   60G   1.4T    60G  /srv/app/uploads  lz4       128K

Interpretación: las decisiones de replicación son por dataset. Aquí, db está sintonizado con recordsize más pequeño.
Si replicas el padre con -R, obtendrás también los hijos—bueno cuando es intencional.

Tarea 2: Crear una instantánea (dataset single)

cr0x@prod:~$ SNAP=auto-2025-12-25_0000
cr0x@prod:~$ zfs snapshot tank/app@${SNAP}

Interpretación: las instantáneas son instantáneas. El dataset sigue cambiando después de la instantánea; la instantánea es una vista consistente en el tiempo.

Tarea 3: Crear instantáneas recursivas (dataset + hijos)

cr0x@prod:~$ SNAP=auto-2025-12-25_0000
cr0x@prod:~$ zfs snapshot -r tank/app@${SNAP}

Interpretación: cada descendiente recibe una instantánea con el mismo nombre. Esa simetría facilita mucho el scripting de replicación.

Tarea 4: Hacer la primera replicación completa a un receptor vacío

cr0x@prod:~$ SNAP=auto-2025-12-25_0000
cr0x@prod:~$ zfs send -R tank/app@${SNAP} | ssh backup 'zfs receive -u -F backup/app'

Interpretación: -R replica el árbol de datasets. receive -u lo mantiene desmontado. -F fuerza un rollback en el receptor si es necesario.
Usa -F solo cuando entiendas el radio de impacto; puede descartar cambios en el receptor.

Tarea 5: Realizar una replicación incremental a la siguiente instantánea

cr0x@prod:~$ OLD=auto-2025-12-25_0000
cr0x@prod:~$ NEW=auto-2025-12-25_0100
cr0x@prod:~$ zfs snapshot -r tank/app@${NEW}
cr0x@prod:~$ zfs send -R -i tank/app@${OLD} tank/app@${NEW} | ssh backup 'zfs receive -u backup/app'

Interpretación: esto envía solo los cambios desde @OLD manteniendo la estructura replicada del dataset.
Si el receptor no tiene @OLD (o tiene un linaje distinto), esto falla.

Tarea 6: Usar -I para incluir instantáneas intermedias

cr0x@prod:~$ BASE=auto-2025-12-25_0000
cr0x@prod:~$ HEAD=auto-2025-12-25_0600
cr0x@prod:~$ zfs send -R -I tank/app@${BASE} tank/app@${HEAD} | ssh backup 'zfs receive -u backup/app'

Interpretación: el receptor acaba con cada instantánea entre @BASE y @HEAD que exista en el emisor, no solo con @HEAD.
Así es como mantienes el historial de instantáneas alineado.

Tarea 7: Confirmar que las instantáneas existen y coinciden en ambos extremos

cr0x@prod:~$ zfs list -t snapshot -o name,creation -s creation -r tank/app | tail -n 5
tank/app@auto-2025-12-25_0200  Wed Dec 25 02:00 2025
tank/app@auto-2025-12-25_0300  Wed Dec 25 03:00 2025
tank/app@auto-2025-12-25_0400  Wed Dec 25 04:00 2025
tank/app@auto-2025-12-25_0500  Wed Dec 25 05:00 2025
tank/app@auto-2025-12-25_0600  Wed Dec 25 06:00 2025
cr0x@backup:~$ zfs list -t snapshot -o name,creation -s creation -r backup/app | tail -n 5
backup/app@auto-2025-12-25_0200  Wed Dec 25 02:01 2025
backup/app@auto-2025-12-25_0300  Wed Dec 25 03:01 2025
backup/app@auto-2025-12-25_0400  Wed Dec 25 04:01 2025
backup/app@auto-2025-12-25_0500  Wed Dec 25 05:01 2025
backup/app@auto-2025-12-25_0600  Wed Dec 25 06:01 2025

Interpretación: los timestamps no coincidirán exactamente, pero los nombres de instantáneas sí deberían. Cuando se desincronizan, tu próximo send incremental es donde lo descubres.

Tarea 8: Estimar el tamaño del envío antes de comprometer (útil para enlaces WAN)

cr0x@prod:~$ zfs send -n -v -i tank/app@auto-2025-12-25_0500 tank/app@auto-2025-12-25_0600
send from @auto-2025-12-25_0500 to tank/app@auto-2025-12-25_0600 estimated size is 3.14G
total estimated size is 3.14G

Interpretación: -n es un dry run; -v imprime estimados. Considéralo una estimación—compresión, recordsize y el contenido del stream pueden sesgar la realidad.

Tarea 9: Añadir compresión y buffering en el pipeline de transporte

cr0x@prod:~$ OLD=auto-2025-12-25_0500
cr0x@prod:~$ NEW=auto-2025-12-25_0600
cr0x@prod:~$ zfs send -R -i tank/app@${OLD} tank/app@${NEW} \
  | lz4 -z \
  | ssh backup 'lz4 -d | zfs receive -u backup/app'

Interpretación: si tus datos ya están comprimidos (medios, backups de backups), esto puede malgastar CPU y reducir el rendimiento.
Si tu cuello de botella es el ancho de banda WAN, puede ser una gran ganancia. Mide, no supongas.

Tarea 10: Replicación de datasets cifrados con envío raw

cr0x@prod:~$ zfs get -H -o property,value encryption tank/secret
encryption	on
cr0x@prod:~$ SNAP=auto-2025-12-25_0000
cr0x@prod:~$ zfs snapshot tank/secret@${SNAP}
cr0x@prod:~$ zfs send -w tank/secret@${SNAP} | ssh backup 'zfs receive -u backup/secret'

Interpretación: el receptor almacena datos cifrados y no necesita la clave para retenerlos.
Tu workflow de restauración debe contemplar dónde viven las claves y cómo las cargarás.

Tarea 11: Usar bookmarks para preservar una base incremental sin conservar instantáneas antiguas

cr0x@prod:~$ zfs snapshot tank/app@auto-2025-12-25_0700
cr0x@prod:~$ zfs bookmark tank/app@auto-2025-12-25_0700 tank/app#base-0700
cr0x@prod:~$ zfs list -t bookmark -o name,creation -r tank/app
NAME                   CREATION
tank/app#base-0700      Wed Dec 25 07:00 2025

Interpretación: los bookmarks son «anclas» ligeras a un estado de instantánea. Pueden usarse como bases incrementales en muchos workflows,
permitiéndote eliminar instantáneas más antiguas mientras mantienes continuidad de replicación—cuando tu plataforma lo soporta end-to-end.

Tarea 12: Manejar un receive interrumpido con tokens de reanudación

cr0x@backup:~$ zfs get -H -o value receive_resume_token backup/app
1-7d3f2b8c2e-120-789c...
cr0x@prod:~$ ssh backup 'zfs get -H -o value receive_resume_token backup/app'
1-7d3f2b8c2e-120-789c...
cr0x@prod:~$ TOKEN=$(ssh backup 'zfs get -H -o value receive_resume_token backup/app')
cr0x@prod:~$ zfs send -t ${TOKEN} | ssh backup 'zfs receive -u backup/app'

Interpretación: reanudas el stream desde donde se detuvo, en lugar de reiniciarlo. Es una de esas funciones que no aprecias hasta que un enlace inestable cae al 97%.

Tarea 13: Podar instantáneas en el emisor de forma segura tras replicación exitosa

cr0x@prod:~$ KEEP=auto-2025-12-25_0600
cr0x@prod:~$ zfs list -H -t snapshot -o name -s creation -r tank/app | head
tank/app@auto-2025-12-24_0000
tank/app@auto-2025-12-24_0100
tank/app@auto-2025-12-24_0200
cr0x@prod:~$ zfs destroy tank/app@auto-2025-12-24_0000
cr0x@prod:~$ zfs destroy -r tank/app@auto-2025-12-24_0100

Interpretación: la eliminación de instantáneas es permanente. En producción, la poda debe automatizarse pero con conservadurismo, idealmente condicionada a:
«el receptor tiene la instantánea X» y «la última replicación tuvo éxito». Si borras la única base común, tu próximo incremental será un envío completo.

Tarea 14: Verificar integridad con un scrub y comprobar errores

cr0x@backup:~$ zpool scrub backup
cr0x@backup:~$ zpool status -v backup
  pool: backup
 state: ONLINE
status: scrub in progress since Wed Dec 25 09:12:48 2025
  1.23T scanned at 2.11G/s, 410G issued at 701M/s, 3.02T total
config:

        NAME        STATE     READ WRITE CKSUM
        backup      ONLINE       0     0     0
          raidz1-0  ONLINE       0     0     0
            sda     ONLINE       0     0     0
            sdb     ONLINE       0     0     0
            sdc     ONLINE       0     0     0
errors: No known data errors

Interpretación: la replicación mueve datos; los scrubs los validan en reposo. Las copias sin comprobaciones periódicas de integridad son solo optimismo caro.

Guía de diagnóstico rápido (encuentra el cuello de botella en minutos)

Cuando la replicación es lenta o falla, tu trabajo es evitar el desvío de 90 minutos donde todos discuten sobre «la red» mientras el culpable real es un solo núcleo de CPU saturado por SSH.
Esta es la secuencia que uso porque converge rápido.

Primero: ¿falla rápido por linaje?

Revisa el mensaje de error y confirma que el receptor tiene la instantánea base (o la cadena correcta de instantáneas).

cr0x@backup:~$ zfs list -t snapshot -o name -r backup/app | tail -n 10
backup/app@auto-2025-12-25_0300
backup/app@auto-2025-12-25_0400
backup/app@auto-2025-12-25_0500
backup/app@auto-2025-12-25_0600

Si el receptor no tiene la instantánea base, la solución no es «reintentar con más ganas». Es «enviar un full» o «enviar un incremental desde la última instantánea común».

Segundo: ¿está el receptor bloqueado por I/O de disco?

La replicación escribe mucho en el receptor. Si el pool de backup es lento, cualquier ajuste upstream es maquillaje en una carretilla.

cr0x@backup:~$ zpool iostat -v 1 5
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
backup                       3.2T  5.1T      0    820      0  210M
  raidz1-0                   3.2T  5.1T      0    820      0  210M
    sda                          -      -      0    275      0   70M
    sdb                          -      -      0    280      0   71M
    sdc                          -      -      0    265      0   69M
--------------------------  -----  -----  -----  -----  -----  -----

Si el ancho de banda de escritura es bajo y la latencia alta (lo verás en herramientas del sistema), el receptor es tu limitador.
Causas comunes: overhead de paridad RAIDZ en escrituras pequeñas, discos SMR, un pool ocupado o un SLOG mal dimensionado para cargas con muchas syncs.

Tercero: ¿está el emisor bloqueado leyendo?

cr0x@prod:~$ zpool iostat -v 1 5
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                         5.8T  2.1T    620      5  640M   2.1M
  mirror-0                   1.9T   700G   210      2  220M   900K
    nvme0n1                      -      -   210      2  220M   900K
    nvme1n1                      -      -   210      2  220M   900K
--------------------------  -----  -----  -----  -----  -----  -----

Si las lecturas son lentas, comprueba si el pool de origen está bajo carga (apps, compactaciones, scrubs) o si estás thrashing el ARC.

Cuarto: ¿es el pipeline de transporte el cuello de botella (SSH, compresión, buffering)?

Observa CPU y throughput durante un send. Un síntoma clásico es un núcleo de CPU clavado y el stream limitado a una tasa sorprendentemente constante.

cr0x@prod:~$ ps -eo pid,comm,%cpu,args | egrep 'zfs|ssh|lz4' | head
21433 zfs      35.2 zfs send -R -i tank/app@auto-2025-12-25_0500 tank/app@auto-2025-12-25_0600
21434 lz4      98.7 lz4 -z
21435 ssh      64.1 ssh backup lz4 -d | zfs receive -u backup/app

Si la compresión está saturando la CPU y no reduce bytes de forma significativa, elimínala. Si SSH está saturando CPU, considera cifrados más rápidos (cuando la política lo permita)
o mover la replicación a una red dedicada con offload de IPsec—cualquier cosa que desplace el coste de la criptografía.

Quinto: ¿está atascado por un receive parcial?

cr0x@backup:~$ zfs get -H -o property,value receive_resume_token backup/app
receive_resume_token  1-7d3f2b8c2e-120-789c...

Si existe un token de reanudación, probablemente tengas un receive interrumpido que debe reanudarse o abortarse antes de que la nueva replicación progrese limpiamente.

Tres micro-relatos del mundo corporativo

Micro-relato 1: El incidente causado por una suposición errónea

Un equipo desplegó replicación ZFS para una flota de servidores de build. La idea era sólida: instantanear el dataset del workspace cada hora, replicar a un host de backup
y conservar una semana de historial. Probaron un envío completo, probaron un incremental y declararon la victoria.

Dos meses después, un servidor de build falló. El día de la restauración llegó con la confianza de un runbook bien ensayado—hasta que zfs receive rechazó
la cadena incremental. El receptor tenía las instantáneas, sí. Pero no las instantáneas correctas: alguien había «limpiado backups antiguos» en el host de backup
para recuperar espacio y había eliminado una instantánea que aún era la base para la siguiente serie incremental.

La suposición errónea fue sutil: «Si borro instantáneas antiguas en el receptor, el emisor aún puede enviar incrementales desde lo que tenga.»
Eso no es cómo funciona ZFS. Los incrementales requieren una instantánea ancestro común. Borra el ancestro y el emisor no puede calcular un delta que el receptor pueda aplicar.
ZFS no inventa una nueva base.

La recuperación fue un envío completo por un enlace que nunca estuvo diseñado para ello. El servicio volvió, pero la lección quedó: la retención de instantáneas en el receptor
no es independiente. Si quieres retenciones independientes, necesitas bases independientes—los bookmarks pueden ayudar, y también una política que pode solo después de confirmar
que la siguiente base incremental existe en ambos extremos.

Tras el incidente, implementaron una comprobación de «última instantánea común» en la automatización y bloquearon borrados en el receptor a menos que la instantánea correspondiente
se hubiera podado primero en el emisor (o a menos que existiera un bookmark base). No fue glamuroso, pero convirtió la replicación de «funciona hasta que no» en un sistema.

Micro-relato 2: La optimización que salió mal

Otra organización tenía un sitio remoto conectado por un WAN modesto. Alguien tuvo la brillante idea de «comprimir todo» en el pipeline de replicación
porque, en papel, la compresión reduce ancho de banda. Insertaron una etapa de compresión y celebraron cuando pruebas tempranas con logs de texto mostraron grandes ganancias.

Luego apuntaron el mismo pipeline a datasets de VM. Las VMs alojaban bases de datos, caches y muchos blobs ya comprimidos. El resultado fue una caída de throughput
y aumento del lag de replicación. La ventana de backup ya no era técnicamente «una ventana»: era simplemente «el tiempo entre ahora y nunca».

El contragolpe fue clásico: la CPU se convirtió en el cuello de botella. La compresión devoraba ciclos y generaba poca reducción. SSH también cifraba el stream ya comprimido,
quemando más CPU en ambos extremos. Los arrays de almacenamiento estaban ociosos mientras las CPUs sudaban.

Arreglarlo no fue heroico. Midieron. Eliminaban compresión para datasets que no se beneficiaban, la mantenían para los que sí, y programaron replicación fuera de horas punta
donde no competiría con la CPU de aplicaciones. Para algunos datasets críticos, hicieron envíos raw cifrados, reduciendo overhead de transformaciones redundantes.

La «optimización» no estaba mal en principio. Estaba mal como política global. En ingeniería de backups, las reglas universales son cómo te ganas un dolor universal.

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

Una empresa consciente de la seguridad usaba datasets cifrados para datos de clientes y los replicaba a un entorno de backup separado. El entorno de backup se trataba
como semi-confiable: buenos controles físicos, pero no lo bastante confiable para almacenar claves de descifrado. Así que usaron sends raw y mantuvieron las claves en un lugar controlado.

Un miércoles que empezó como cualquier otro, un controlador de almacenamiento en el sistema primario empezó a lanzar errores intermitentes. Nada catastrófico—solo lo suficiente
para sembrar dudas. Iniciaron un failover controlado: restaurar las instantáneas más recientes a un cluster standby y cortar el tráfico. No era la primera vez que ejecutaban el procedimiento; lo practicaban trimestralmente, como adultos.

La restauración fue fluida porque las copias no eran solo «presentes», eran predecibles: los datasets recibidos estaban desmontados, las propiedades eran consistentes
y el conjunto de instantáneas estaba completo. Lo más importante, tenían un programa de scrub en el pool de backup y monitorizaban errores de checksum como métrica de pager.
Cuando llegó el momento de confiar en las copias, no confiaron en la esperanza.

El equipo bromeó después que la parte más emocionante del incidente fue lo poco emocionante de la recuperación. Ese es el resultado correcto. Si tu historia de DR es emocionante,
probablemente también será cara.

Errores comunes, síntomas y soluciones

Error 1: Borrar la instantánea base (o desviar el receptor)

Síntomas: el receive incremental falla con mensajes sobre instantáneas faltantes o «does not exist», o «incremental source is not earlier than destination.»

Solución: identifica la última instantánea común y replica desde ahí. Si no existe ninguna, haz un envío completo. Prevén recurrencias coordinando retención y/o usando bookmarks.

Error 2: Usar zfs receive -F por reflejo

Síntomas: «Funcionó» pero un dataset en el receptor perdió instantáneas más nuevas o se hizo rollback inesperadamente.

Solución: reserva -F para casos donde el receptor es estrictamente un target de replicación y aceptas la semántica de rollback. Prefiere un namespace objetivo limpio y promociones controladas.

Error 3: Replicar montajes en el namespace del host de backup

Síntomas: datasets se montan en el host de backup; agentes de indexación/AV consumen I/O; admins navegan y modifican copias de backup por accidente (o lo intentan).

Solución: siempre recibe con -u, y considera establecer canmount=off en las raíces de replicación del receptor.

Error 4: Confundir -i y -I

Síntomas: el receptor tiene solo la instantánea más reciente, no el historial intermedio; las políticas de retención no coinciden; las restauraciones carecen del punto en el tiempo esperado.

Solución: usa -I cuando quieras incluir instantáneas intermedias. Usa -i cuando realmente quieras solo el delta entre dos instantáneas.

Error 5: Asumir que «incremental» significa «pequeño» (especialmente para imágenes de VM)

Síntomas: incrementales tan grandes como envíos completos; la replicación se retrasa; las redes se saturan inesperadamente.

Solución: mide estimados de envío (zfs send -n -v), ajusta recordsize del dataset adecuadamente y considera layouts conscientes de la carga (datasets separados para datos con alta churn).

Error 6: No planificar interrupciones

Síntomas: receives parciales bloquean replicación futura; reinicios repetidos desperdician horas; el retraso de backups crece.

Solución: usa receive reanudable y monitoriza receive_resume_token. Añade automatización para reanudar o abortar limpiamente según la política.

Error 7: Sorpresas por desajustes de versión / características

Síntomas: el receive falla con mensajes sobre características no soportadas o versiones de stream.

Solución: estandariza versiones de OpenZFS entre emisor/receptor cuando sea posible, o limita envíos a características compatibles mediante opciones específicas de la plataforma y una secuencia disciplinada de actualizaciones.

Listas de verificación / plan paso a paso

Checklist A: Construir una línea base de replicación sensata (configuración inicial)

  1. Elige una raíz de dataset para replicación (p. ej., tank/app) y decide si los hijos se incluyen.
  2. Define el nombrado de instantáneas: prefijo + tiempo ordenable.
  3. En el receptor, crea un namespace dedicado de pool/dataset (p. ej., backup/app) y asegúrate de que tenga espacio suficiente para la retención.
  4. Configura defaults en el receptor: canmount=off en la raíz, y planifica siempre usar zfs receive -u.
  5. Crea la primera instantánea recursiva: zfs snapshot -r tank/app@auto-....
  6. Haz el primer envío completo con -R y recibe desmontado.
  7. Valida la presencia de instantáneas en ambos extremos.
  8. Decide la retención e impleméntala con cuidado (podar después de confirmar que el receptor tiene una base segura).

Checklist B: Operaciones diarias (la rutina para no recibir pager)

  1. Crea instantáneas según el calendario (hourly/daily).
  2. Replica incrementales desde la última instantánea replicada hasta la nueva instantánea.
  3. Tras la replicación, valida: la última instantánea existe en el receptor y receive_resume_token está vacío.
  4. Podar instantáneas antiguas en el emisor (y opcionalmente en el receptor) según la política sin romper la cadena.
  5. Ejecuta scrubs periódicos en el receptor y alerta por errores de checksum.
  6. Haz ejercicios de restauración: monta un clone de instantánea recibida y verifica integridad a nivel de aplicación.

Checklist C: Workflow de restauración (a nivel de filesystem)

  1. Identifica la instantánea que quieres restaurar.
  2. Clónala a un nuevo dataset en el objetivo de restauración (evita sobrescribir datos en producción durante la investigación).
  3. Monta el clone, valida contenidos y luego promuévelo o cópialo en su lugar según las necesidades de la app.

Preguntas frecuentes

1) ¿Cuál es la diferencia entre zfs send -i y -I?

-i envía el delta entre exactamente dos instantáneas. -I envía un rango de replicación y puede incluir instantáneas intermedias, preservando el historial.
Si te importa conservar muchos puntos de restauración en el receptor, -I suele ser el martillo correcto.

2) ¿Puedo hacer envíos incrementales sin mantener instantáneas antiguas por siempre?

Necesitas alguna base común. En muchos entornos, los bookmarks pueden actuar como bases ligeras, permitiéndote borrar instantáneas mientras conservas la continuidad de replicación.
Si eso encaja depende de tu versión de OpenZFS y workflow. Sin una base (instantánea o bookmark), estás haciendo un envío completo.

3) ¿Por qué falla zfs receive aunque los nombres de instantánea coincidan?

Porque importa el linaje, no solo los nombres. El receptor debe tener la misma instantánea base en contenido y ascendencia GUID. Si la instantánea del receptor fue creada independientemente
o el dataset se hizo rollback/modificó de forma que diverge, ZFS rechazará el stream incremental.

4) ¿Debería replicar propiedades?

A menudo sí, porque propiedades como recordsize, compression y acltype pueden afectar cómo se comportan las restauraciones. Pero sé intencional:
en targets de backup quizá quieras comportamientos de montaje distintos (canmount=off, readonly=on) que en producción.

5) ¿Es seguro ejecutar replicación mientras la aplicación escribe?

Sí, porque replicas una instantánea, que es consistente. El dataset en vivo sigue cambiando, pero la instantánea es una vista estable.
La clave es snapshotear en la capa correcta: para bases de datos, las instantáneas consistentes a nivel de aplicación pueden requerir hooks pre/post (flush, freeze o checkpoint).

6) ¿Cómo sé si el cifrado SSH es mi cuello de botella?

Si el throughput está capado y el uso de CPU es alto en un núcleo durante los sends, sospecha de SSH. Valida observando el consumo de CPU de procesos ssh
durante la replicación y comparando rendimiento en un enlace local de confianza o con distintas configuraciones de transporte aprobadas por tu política de seguridad.

7) ¿Qué me dice realmente zfs send -n -v?

Estima el tamaño del stream sin enviarlo. Es útil para planificar y detectar «por qué este incremental es enorme», pero no es un contador de facturación.
Trátalo siempre como direccional, no absoluto.

8) ¿Deben montarse las copias en el servidor de backup?

En la mayoría de entornos de producción, no. Manténlas desmontadas para reducir lecturas/escrituras accidentales, indexación y curiosidad humana. Monta solo al restaurar o probar.
Usa zfs receive -u y considera canmount=off en raíces replicadas.

9) ¿Puedo replicar un dataset cifrado a un host de backup no confiable?

Sí, con sends raw. El host de backup almacena ciphertext y no necesita claves para recibirlo. Tu proceso de restauración necesita acceso a las claves en un sistema confiable.

10) ¿Con qué frecuencia debo hacer scrub del pool de backup?

Lo suficiente para detectar corrupción silenciosa antes de que se convierta en tu historia de restauración. Muchos operadores hacen mensual en pools grandes y con más frecuencia en pools pequeños,
con alertas en cualquier error de checksum. La cadencia «correcta» depende del tipo de discos, tamaño del pool y cuánto valoras dormir tranquilo.

Conclusión

El send incremental de ZFS es uno de los raros mecanismos de copia que escala desde «un servidor en un armario» hasta «una flota con presión real de cumplimiento»
sin cambiar sus fundamentos. Las instantáneas crean puntos estables en el tiempo. Los streams incrementales mueven solo bloques cambiados. Los receptores reconstruyen el historial
con comprobaciones de integridad incorporadas en el propio sistema de archivos.

La trampa operativa es el linaje: los incrementales requieren una base compartida, y tus políticas de retención, convenciones de nombres y automatización deben respetarlo.
Haz ese trabajo y obtendrás copias rápidas, verificables y fáciles de restaurar—sin volver a copiarlo todo cada vez.

← Anterior
Respaldos de MySQL vs PostgreSQL en contenedores: cómo evitar respaldos falsos
Siguiente →
DNS: Problemas de MTU pueden romper DNS — Cómo probarlo y solucionarlo

Deja un comentario