Estás replicando ZFS como un adulto. Snapshots, incrementales, quizás cifrado, quizá algo de compresión.
Entonces el equipo hace una “pequeña limpieza”: renombra un dataset, mueve un subárbol o reorganiza mountpoints.
La replicación que funcionó durante meses ahora falla con un críptico “does not match incremental source” o recibe datos en un lugar extraño.
La buena noticia: ZFS puede manejar renombrados y movimientos sin drama. La mala noticia: solo lo hace si lo diseñas
pensando en ello, y si dejas de asumir que los nombres de datasets son identidades. No lo son. Son etiquetas.
El modelo mental: qué se rompe y por qué
La replicación ZFS no es “copiar una carpeta por SSH.” Es reproducir transacciones del sistema de archivos de un grafo de almacenamiento
en otro, usando un stream creado por zfs send y aplicado por zfs receive.
Un snapshot es un punto nombrado en el tiempo para un dataset. Un stream incremental es “cambios desde el snapshot A hasta el snapshot B.”
Si el receptor no tiene exactamente el snapshot A en el dataset que estás apuntando, no puede aplicar la diff.
Aquí está la trampa: la gente trata los nombres de datasets como identificadores estables. Automatizan pipelines de replicación así:
“enviar pool/app al backup como backup/app.” Luego alguien renombra pool/app a
pool/apps/app. En el origen, la línea de snapshots sigue existiendo, pero ahora vive bajo un nuevo nombre.
En el destino, tu pipeline todavía intenta aplicar incrementales al antiguo dataset. Boom.
ZFS sí rastrea datasets por GUIDs internos, pero el flujo send/receive sigue dependiendo de nombres de snapshots coincidentes y
de seleccionar el objetivo de receive correcto. Por eso “renombrar o mover” no es inherentemente peligroso—lo peligroso es
diseñar la replicación alrededor de nombres y mountpoints en vez de raíces de dataset controladas y mapeos previsibles.
Chiste #1: Renombrar un dataset es como cambiar tu nombre visible en Slack y esperar que nómina lo siga. No es ese tipo de sistema.
Qué significan “rename” y “move” en términos de ZFS
- Rename cambia el nombre del dataset (por ejemplo,
tank/app→tank/apps/app), preservando snapshots y hijos. - Move suele ser la misma operación: estás renombrando un subárbol bajo un nuevo padre.
- Cambio de mountpoint modifica dónde aparece en el sistema operativo (propiedad
mountpoint), no la identidad del dataset. - Promoción cambia la ascendencia de clones. Esto puede invalidar suposiciones sobre cuál snapshot es “la base” para incrementales.
Cuándo falla realmente la replicación
La replicación falla cuando al menos una de estas cosas sucede:
- Envías incrementales desde el snapshot X, pero el dataset receptor no tiene el snapshot X.
- Recibes en un dataset distinto al de la vez anterior (a menudo debido a scripts que mapean nombres incorrectamente después de un rename).
- El receptor ha divergido con escrituras locales (alguien lo montó en modo lectura/escritura y lo modificó), así que un receive de avance rápido es imposible sin rollback.
- Cambiaste el modo de replicación (p. ej., de “plain” a “raw encrypted”) a mitad de flujo sin reseed.
- Usaste
zfs recvsin-uy propiedades de mountpoint causaron conflictos, por lo que el receive falla o monta en rutas de producción.
El tema: ZFS es consistente. Los humanos son creativos.
Hechos y contexto histórico que importan en la vida real
- Los snapshots de ZFS son baratos porque son solo referencias de metadatos a bloques existentes; solo los bloques cambiados consumen espacio nuevo.
- El send incremental depende de ascendencia común; no es “diff por nombre”, es “diff por línea de snapshots.” Faltar la snapshot base es fatal.
- Renombrar un dataset preserva snapshots y hijos; los objetos snapshot no desaparecen solo porque cambió la etiqueta del dataset.
- Los bookmarks existen para mantener una base incremental sin guardar un snapshot; son punteros ligeros a un grupo de transacciones.
- ZFS receive es transaccional; un receive fallido no aplica datos a medias en el caso normal. Por eso es una primitiva fuerte para automatización.
- Las propiedades pueden incluirse o excluirse en la replicación (p. ej., vía
zfs send -p), lo que afecta mountpoints y puede sorprenderte. - Los sends raw encriptados preservan el cifrado y no exponen texto plano al userspace del host emisor, pero requieren soporte de características consistente.
- La replicación uno-a-muchos escala mejor que improvisar muchos-a-uno; los streams ZFS son determinísticos, pero tu lógica de mapeo ad-hoc no lo es.
Una cita operativa que ha envejecido bien: La esperanza no es una estrategia.
— Gene Kranz.
Reglas de diseño: replicación que sobrevive renombrados
Regla 1: Elige una raíz de replicación y nunca la reutilices
Crea un dataset “raíz de replicación” estable en ambos extremos y trátalo como un límite de espacio de nombres.
Ejemplo: todo lo que replicas vive bajo tank/replica-src en el origen y bkp/replica en el destino.
Los datasets de aplicación pueden moverse en producción, pero la replicación siempre apunta a la ruta raíz estable en cada sistema.
En la práctica, esto significa replicar datasets en un subárbol dedicado y exponerlos a consumidores vía clones, override de mountpoint,
o datasets “publicados” separados. No dejes que la ruta objetivo de replicación sea igual a tus mountpoints de producción.
Si lo haces, estás a un error tipográfico de montar un dataset de backup sobre un directorio en vivo. Eso no es estrategia de backup; es arte de performance.
Regla 2: Separa “qué replicar” de “dónde vive hoy”
El nombre del dataset origen puede cambiar. Lo que no debería cambiar es la intención: “este es el dataset que respalda el servicio X.”
Codifica la intención con una propiedad o un archivo de mapeo, no analizando nombres de datasets.
Un buen patrón es etiquetar:
org.example:replicate=true y org.example:replica-name=svc-x.
Tu automatización descubre datasets por propiedad y luego los replica a una ruta destino fija como
bkp/replica/svc-x. Renombra el origen cuanto quieras; la identidad de replicación permanece estable.
Regla 3: Mantén un esquema consistente de nombres de snapshot a través de movimientos
Los nombres de snapshot son parte de tu contrato. Si replicas incrementales, ambos lados necesitan el mismo nombre de snapshot
para la base y el objetivo. Elige un esquema y no te compliques:
replica-YYYYMMDD-HHMM o replica-auto-###.
Si debes cambiar el esquema, reseed con un envío completo una vez y reinicia los incrementales. No intentes “puentear” con magia.
Regla 4: Usa holds y bookmarks intencionalmente
Si dependes de una snapshot base para incrementales, protégela. Usa zfs hold para evitar que el pruning la borre
antes de que el receptor haya alcanzado la copia. Si quieres evitar mantener snapshots antiguas para siempre, usa bookmarks:
permiten hacer incrementales desde un “punto recordado” sin retener la snapshot completa.
Regla 5: Nunca dejes que el objetivo diverja (a menos que lo quieras)
Tu objetivo de replicación debe tratarse como solo-lectura. Impónlo socialmente (permisos), operativamente (no montarlo automáticamente),
y técnicamente (considera readonly=on en datasets recibidos). La divergencia es la razón número 1 por la que terminas usando
zfs recv -F bajo presión, que es el equivalente ZFS de “reinicio la prod, ¿qué puede salir mal?”
Regla 6: Decide tu estrategia de mapeo de receive desde el principio
Tienes tres patrones comunes:
- Mirror de nombres:
tank/app→bkp/tank/app. Fácil hasta que renombrás. Entonces necesitas lógica de manejo de renames. - Nombres destino estables: etiqueta los datasets origen y mapea a
bkp/replica/<service>. Mejor para tolerancia a renombrados/movimientos. - Recibir bajo un padre con
zfs recv -d: preserva la disposición del subárbol bajo una raíz dedicada. Bueno para migración masiva, pero aún acoplado a nombres.
Para “sin drama”, los nombres destino estables ganan. Cuestan un poco de configuración y ahorran muchas madrugadas.
Libro de tareas: comandos, salidas y decisiones (12+)
Estas son las tareas que realmente ejecuto cuando la replicación se pone rara—más lo que significa la salida y qué decisión impulsa.
Los hostnames son ejemplos: src1 es origen, bkp1 es backup.
Task 1: Identificar qué cambió (evidencia de rename/move)
cr0x@server:~$ zfs list -t filesystem -o name,creation -r tank | head
NAME CREATION
tank Mon Jan 8 09:12 2024
tank/apps Tue Apr 2 13:41 2024
tank/apps/app1 Tue Apr 2 13:44 2024
tank/oldapp Wed Mar 6 10:18 2024
Qué significa: Esto te da nombres actuales y una línea temporal aproximada.
Si un dataset “se movió”, verás un nuevo camino padre; la hora de creación típicamente permanece igual para el dataset en sí.
Decisión: Si el nombre del dataset cambió, deja de depender del mapeo por nombre. Cambia a mapeo por propiedad o renombra explicitamente el destino.
Task 2: Confirmar presencia y nombres de snapshots en el origen
cr0x@server:~$ zfs list -t snapshot -o name,creation -s creation -r tank/apps/app1 | tail -5
tank/apps/app1@replica-20240130-0200 Tue Jan 30 02:00 2024
tank/apps/app1@replica-20240131-0200 Wed Jan 31 02:00 2024
tank/apps/app1@replica-20240201-0200 Thu Feb 1 02:00 2024
tank/apps/app1@replica-20240202-0200 Fri Feb 2 02:00 2024
tank/apps/app1@replica-20240203-0200 Sat Feb 3 02:00 2024
Qué significa: Tienes una serie de snapshots consistente.
Decisión: Elige el último snapshot replicado con éxito como base incremental. Si no puedes identificarlo, inspecciona el destino.
Task 3: Confirmar presencia de snapshots en el dataset destino que crees correcto
cr0x@server:~$ ssh bkp1 zfs list -t snapshot -o name -r bkp/replica/app1 | tail -5
bkp/replica/app1@replica-20240130-0200
bkp/replica/app1@replica-20240131-0200
bkp/replica/app1@replica-20240201-0200
bkp/replica/app1@replica-20240202-0200
bkp/replica/app1@replica-20240203-0200
Qué significa: El destino tiene las mismas snapshots, por lo que los incrementales deberían funcionar.
Decisión: Si las snapshots no coinciden, o estás recibiendo en el dataset equivocado o necesitas reseed/envío completo.
Task 4: Comprobar divergencia (escrituras en el destino)
cr0x@server:~$ ssh bkp1 zfs get -H -o property,value,source readonly bkp/replica/app1
readonly off local
Qué significa: El destino es escribible. Eso no demuestra que haya divergencia, pero es una invitación.
Decisión: Establece readonly=on en objetivos de replicación a menos que tengas un flujo de failover deliberado.
Task 5: Encontrar rápidamente el último snapshot común
cr0x@server:~$ comm -12 \
<(zfs list -H -t snapshot -o name -r tank/apps/app1 | sed 's/^.*@/@/' | sort) \
<(ssh bkp1 zfs list -H -t snapshot -o name -r bkp/replica/app1 | sed 's/^.*@/@/' | sort) | tail -3
@replica-20240201-0200
@replica-20240202-0200
@replica-20240203-0200
Qué significa: Los nombres de snapshot coinciden entre origen/destino. La última línea es tu candidata a base incremental.
Decisión: Usa esa base para zfs send -i (o -I si envías un rango que incluya snapshots intermedias).
Task 6: Dry-run para estimar tamaño del send (chequeo de sanidad)
cr0x@server:~$ zfs send -nPv -i tank/apps/app1@replica-20240203-0200 tank/apps/app1@replica-20240204-0200
send from @replica-20240203-0200 to tank/apps/app1@replica-20240204-0200 estimate: 1.42G
total estimated size is 1.42G
Qué significa: Vas a enviar ~1.4 GiB. Si esperabas 10 MiB, algo anda mal (o tu app se puso ocupada).
Decisión: Si la estimación es inesperadamente grande, verifica churn (logs, tmp, bases de datos) y considera excluir o repensar el layout del dataset.
Task 7: Ejecutar la replicación incremental con comportamiento de receive explícito
cr0x@server:~$ zfs send -w -i tank/apps/app1@replica-20240203-0200 tank/apps/app1@replica-20240204-0200 | \
ssh bkp1 zfs recv -u bkp/replica/app1
Qué significa: -w envía raw (encriptado si el dataset está cifrado). -u evita auto-montar en el host de backup.
Si esto falla, obtendrás un error específico; no lo “arregles” con -F hasta entender el desajuste.
Decisión: Si el receive falla por falta de snapshot base, detente y reconcilia los datasets. Si falla por conflictos de mountpoint, mantén -u y corrige las propiedades.
Task 8: Verificar que el receive realmente aterrizó donde piensas
cr0x@server:~$ ssh bkp1 zfs list -o name,used,refer,mountpoint bkp/replica/app1
NAME USED REFER MOUNTPOINT
bkp/replica/app1 8.21G 8.21G /bkp/replica/app1
Qué significa: El dataset existe y el mountpoint es lo que el destino cree que debe ser.
Decisión: Si el mountpoint apunta a una ruta de producción, detente y establece mountpoint=none o recibe con -u y ajusta antes de montar.
Task 9: Inspeccionar errores del lado receptor y estado parcial
cr0x@server:~$ ssh bkp1 zpool status -x
all pools are healthy
Qué significa: El pool no está degradado. Si la replicación es lenta o falla, el problema probablemente no sea un vdev muriendo activamente.
Decisión: Si zpool status muestra resilvering, errores o un vdev degradado, arregla la salud del almacenamiento antes de culpar a zfs send/recv.
Task 10: Encontrar bloqueadores ocultos: holds que impiden pruning
cr0x@server:~$ zfs holds tank/apps/app1@replica-20240203-0200
NAME TAG TIMESTAMP
tank/apps/app1@replica-20240203-0200 repl-base Sat Feb 3 02:05 2024
Qué significa: Existe un hold. Genial cuando es intencional; molesto cuando se olvidó.
Decisión: Mantén holds sobre la última base replicada hasta que el destino confirme la recepción. Libera los holds como parte de un hook post-replicación.
Task 11: Usar un bookmark como ancla incremental (cuando quieres podar snapshots)
cr0x@server:~$ zfs bookmark tank/apps/app1@replica-20240203-0200 tank/apps/app1#repl-anchor
cr0x@server:~$ zfs list -t bookmark -o name -r tank/apps/app1
tank/apps/app1#repl-anchor
Qué significa: El bookmark existe. Puede servir como el lado “from” de incrementales incluso si luego destruyes la snapshot.
Decisión: Usa bookmarks cuando la retención sea agresiva pero la replicación aún necesite una base estable.
Task 12: Comprobar feature flags y compatibilidad de cifrado (evitar sorpresas feas)
cr0x@server:~$ zpool get -H -o name,property,value feature@encryption tank
tank feature@encryption active
cr0x@server:~$ ssh bkp1 zpool get -H -o name,property,value feature@encryption bkp
bkp feature@encryption active
Qué significa: Ambos pools soportan características de cifrado. Los sends raw no fallarán por falta de soporte de característica.
Decisión: Si el destino carece de características necesarias, actualiza o evita workflows raw/encriptados hasta alinear capacidades.
Task 13: Diagnosticar límites de throughput durante la replicación
cr0x@server:~$ iostat -xz 1 3
Linux 6.6.0 (src1) 02/04/2026 _x86_64_ (16 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
12.10 0.00 3.20 18.40 0.00 66.30
Device r/s w/s rkB/s wkB/s await svctm %util
nvme0n1 82.0 120.0 98000.0 62000.0 8.20 0.45 91.0
Qué significa: Alto %util y await elevado sugieren que el almacenamiento es el cuello de botella (o al menos está muy exigido).
Decisión: Si los discos están saturados, limita la replicación, prográmala fuera de pico, o ajusta recordsize/compression para la carga de trabajo.
Task 14: Validar que no estás cambiando propiedades accidentalmente vía replicación
cr0x@server:~$ zfs get -H -o property,value,source mountpoint,canmount tank/apps/app1
mountpoint /var/lib/app1 local
canmount on default
cr0x@server:~$ ssh bkp1 zfs get -H -o property,value,source mountpoint,canmount bkp/replica/app1
mountpoint /bkp/replica/app1 local
canmount noauto local
Qué significa: El dataset de backup no se montará automáticamente (noauto). Eso es saludable para una réplica.
Decisión: Fuerza que los objetivos de backup sean inertes por defecto: canmount=noauto, montajes controlados sólo durante pruebas de restauración.
Manejo de renombrados y movimientos de datasets (patrones seguros)
Patrón A: Identidad basada en propiedades con nombres destino estables (recomendado)
Este es el enfoque “adulto en la sala”. Etiquetas el dataset con una identidad que no cambia cuando se renombra,
luego replicas a un dataset destino estable nombrado según esa identidad.
Cómo funciona:
- En el dataset origen (dondequiera que esté ahora), establece una propiedad como
org.example:replica-name=app1. - Tu herramienta de replicación descubre datasets por propiedad, no por ruta.
- Todos los streams aterrizan en
bkp/replica/app1, sin importar si el origen estank/app1otank/apps/app1.
Beneficio operativo: Los renombrados no importan. Los movimientos no importan. Solo importa la ascendencia, y la mantienes estable.
Qué puede seguir fallando: trucos de promoción/clone, cambios de modo de cifrado, o permitir que el destino diverja.
Patrón B: Si replicás nombres, tratá el rename como un evento replicado
Algunos entornos quieren que la ruta destino refleje la ruta origen para conveniencia de navegación.
Está bien. Pero entonces debés replicar renombres como un cambio de primera clase.
ZFS no envía “eventos de rename” como cosas separadas. Tu automatización debe detectar renombres (o movimientos)
y aplicar renombres coincidentes en el destino antes de enviar incrementales.
Enfoque mínimo:
- Mantén un archivo de mapeo de GUID de dataset del origen → ruta del dataset destino.
- En cada ejecución, compara el nombre actual del origen para ese GUID. Si cambió, renombra el dataset destino en consecuencia.
- Después ejecuta los incrementales como siempre.
Esto funciona porque renombrar un dataset no cambia sus snapshots ni su GUID. Tu trabajo es solo mantener alineada la ruta del dataset receptor
con donde tu automatización espera encontrarlo.
Patrón C: Usar zfs recv -d bajo una raíz estable para movimientos de subárboles
Si querés preservar nombres relativos pero no nombres absolutos, recibe bajo una raíz estable con -d.
Ejemplo: el origen envía tank/apps/app1, el destino recibe bajo bkp/replica con -d,
creando bkp/replica/tank/apps/app1 o similar según el stream de envío y las opciones objetivo.
Esto puede ser bueno para “día de migración” o replicación masiva, pero sigue acoplado a nombres. Si la ruta origen cambia,
ahora crearás una nueva ruta en el destino a menos que también renombres la antigua.
Qué hacer durante una ventana de renombrado
Si un rename/move está planificado, podés hacerlo aburrido:
- Pausar la replicación brevemente (o al menos detener el pruning automático).
- Tomar un snapshot justo antes del rename:
@replica-pre-rename. - Renombrar/mover el dataset.
- Confirmar que las snapshots siguen existiendo bajo el nuevo nombre.
- Actualizar el mapeo/propiedad si es necesario.
- Reanudar la replicación usando el último snapshot común.
La idea no es “evitar renombres.” La idea es “evitar renombres más automatización que asume que los nombres nunca cambian.”
Chiste #2: Si tu script de replicación usa cut -d/ -f2 para identificar datasets, no es automatización—es un pedido de ayuda.
Tres micro-historias corporativas desde las trincheras
Micro-historia 1: El incidente causado por una suposición errónea
Una compañía mediana ejecutaba replicación ZFS de un filer de producción a un filer de backup cada 15 minutos. El script de replicación era
simple: listaba datasets bajo tank/prod, luego reflejaba la misma ruta bajo bkp/prod.
Todos pensaban en la ruta del dataset como la identidad de los datos. Había sido estable por años, ¿por qué no?
Un equipo de plataforma reorganizó el árbol de datasets para coincidir con un nuevo organigrama. Nada malintencionado, nada apresurado:
tank/prod/finance se convirtió en tank/prod/business/finance. Los datasets se renombraron correctamente con ZFS,
snapshots e hijos intactos. Incluso enviaron un aviso de cambio. La replicación, sin embargo, no tuvo idea de que
finance había “movido.” Intentó enviar incrementales para tank/prod/business/finance hacia
bkp/prod/business/finance, que aún no existía. Así que lo creó, recibió una baseline completa y siguió.
El filer de backup ahora tenía dos copias: la antigua bkp/prod/finance atrapada en el snapshot de ayer, y la nueva
bkp/prod/business/finance con los snapshots de hoy. Nadie lo notó porque la monitorización solo comprobaba
que “el job de replicación tuvo éxito” y que “el último snapshot existe en alguna parte.”
Cuando se necesitó una restauración, el on-call tomó el nombre obvio—bkp/prod/finance—y lo encontró obsoleto.
Pánico, escalada, sala de guerra, culpa. Los datos estaban ahí, solo bajo una nueva ruta. La restauración se logró tras un retraso,
y la lección caló: la ruta del dataset nunca fue la identidad; era una etiqueta de conveniencia.
La solución fue aburrida: mapeo por propiedad a un nombre destino estable. Mantuvieron el árbol de navegación espejo para humanos,
pero la replicación apuntó a un dataset “ID de servicio” estable y publicaron clones legibles para conveniencia.
Eliminó toda una clase de fallos silenciosos.
Micro-historia 2: La optimización que se volvió contra ellos
Una empresa más grande quería que la replicación fuera “lo bastante rápida para ignorarla.” Alguien ajustó el pipeline:
sends en paralelo, compresión pesada en userspace y pruning agresivo de snapshots para reducir el conteo de datasets.
Se vio bien en las primeras pruebas—los benchmarks aman discos vacíos y cargas calmadas.
El primer contratiempo fue la CPU. La compresión en la tubería compitió con cifrado, checksums y la carga normal.
La replicación se movía más rápido, pero todo lo demás se volvió más lento. También tuvieron picos de latencia porque los jobs de replicación
coincidían con procesos batch de la aplicación. Los usuarios se quejaron. La solución debería haber sido “programarlo,” pero el equipo
insistió: más paralelismo, más afinamientos.
El segundo contratiempo fue más sutil. El pruning de snapshots se volvió tan agresivo que la “última snapshot común”
a veces desaparecía antes de que el receptor recibiera los incrementales (hiccup de red, ventana de mantenimiento, lo que sea).
El sistema entonces caía en envíos completos. Los envíos completos eran enormes, así que tardaban más, por lo que más incrementales se perdían,
y más envíos completos sucedían. Fue un bucle de feedback positivo autoinfligido. El sistema de backup no estaba roto; le pedían que sprintara mientras le pateaban los tobillos.
El contratiempo final fue la complejidad operativa. Cuando un dataset se renombraba, el pipeline paralelo intentaba “auto-sanear”
creando nuevos datasets destino y reseedear. Eso ocultó el problema real (un cambio de mapeo) detrás del “éxito.”
Las restauraciones se volvieron una búsqueda del tesoro.
Terminaron quitando la mayoría de las “optimizaciones” y sustituyéndolas por dos cosas: holds/bookmarks para garantizar una base incremental,
y una regla estricta de que los objetivos de replicación deben ser estables e inertes (canmount=noauto, readonly=on).
El rendimiento mejoró porque el sistema dejó de hacer envíos completos innecesarios. La optimización más barata suele ser no hacer trabajo extra.
Micro-historia 3: La práctica aburrida pero correcta que salvó el día
Una compañía con requisitos regulatorios ejecutaba pruebas de restauración semanales. No “lo montamos una vez el año pasado”, sino una ejecución documentada:
el dataset recibido permanece desmontado, clonan un snapshot en un dataset temporal, asignan un mountpoint seguro, lo montan,
ejecutan cheques de integridad a nivel de aplicación y luego destruyen el clone. Cada semana. Mismos pasos. Mismo registro.
Era tan aburrido que se volvió chiste. Pero forzó disciplina: los objetivos de replicación se mantenían read-only,
nadie usaba las réplicas como espacio de trabajo, y el equipo constantemente ejercitaba los comandos exactos que necesitarían en un incidente.
También notaron pequeñas roturas temprano: un desajuste de nombres de snapshot, una política de prune demasiado agresiva, un receive
que empezó a fallar por un cambio de propiedad.
Entonces llegó un incidente real: un ransomware que obligó a una restauración rápida para un servicio específico.
El dataset del servicio se había renombrado dos semanas antes durante una limpieza. El nombre de producción cambió, pero la propiedad de identidad
de replicación se mantuvo. El dataset de backup estaba exactamente donde el runbook lo esperaba: bkp/replica/svc-payments.
La restauración no fue “fácil”, pero sí predecible. Clonaron el último snapshot bueno, lo montaron en una ruta aislada,
validaron datos y luego lo promovieron al servicio. El rename no importó. La prueba semanal de restauración ya había probado
el mapeo y los pasos. Las prácticas aburridas no reciben aplausos. Te devuelven el fin de semana.
Playbook de diagnóstico rápido (encuentra el cuello de botella rápido)
Cuando la replicación se ralentiza o falla, no empieces a alternar flags al azar. Terminarás con un lío que “funciona” pero no puede restaurarse limpiamente.
Ejecuta esto en orden. El objetivo es identificar si el cuello de botella es lineage, mapeo, salud del almacenamiento,
CPU o red.
Primero: probar lineage y mapeo (la mayoría de fallos)
- Confirma que estás recibiendo en el dataset previsto: lista snapshots objetivo para la ruta destino y confirma que contiene la serie esperada.
- Encuentra el último snapshot común usando la intersección rápida (ver Task 5). Si no hay ninguno, estás reseedeando.
- Revisa divergencia: ¿el destino es escribible, está montado o lo usa algo? Si sí, asume divergencia hasta demostrar lo contrario.
Segundo: comprueba salud del pool y presión de disco
- Salud del pool:
zpool status -xdebe estar limpio. Si no, deja de tratar la replicación como problema principal. - Saturación de disco:
iostat -xzy estadísticas específicas de ZFS (si tu OS las provee) para ver si estás I/O bound. - Espacio disponible: poco espacio libre rompe la replicación de formas tontas y empeora la fragmentación.
Tercero: comprueba CPU y red
- CPU: si usas compresión en la tubería, cifrado o checksumming pesado, vigila steal y tiempo de usuario.
- Red: verifica throughput y pérdida de paquetes. Los streams de replicación son sensibles a enlaces inestables porque los reintentos cuestan tiempo y las bases pueden caducar.
- Picos de latencia: programa replicación lejos de jobs batch de aplicaciones, scrubs y resilvers.
Resultados de decisión
- Si lineage/mapeo está mal: arregla el mapeo del dataset (renombra dataset destino, cambia mapeo por propiedad), no “forces receive”.
- Si el almacenamiento está enfermo: repara/reemplaza y luego reintenta replicación; forzar receives en pools inestables es cómo causas segundos incidentes.
- Si recursos están saturados: limita replicación, reduce paralelismo o prográmala. “Más rápido” no es “mejor” si rompe el RPO.
Errores comunes: síntoma → causa raíz → solución
1) “cannot receive incremental stream: most recent snapshot does not match”
Síntoma: El receive falla al aplicar un stream incremental.
Causa raíz: El dataset destino no tiene exactamente la snapshot base que el stream espera (objetivo equivocado, base pruned o historial divergente).
Solución: Identifica el último snapshot común (Task 5). Si no hay ninguno, reseed con un envío completo al destino correcto. Deja de podar bases antes de que la replicación confirme éxito.
2) Los incrementales se convirtieron de repente en envíos completos después de un rename
Síntoma: Tras un movimiento de dataset, el sistema “amablemente” crea un nuevo dataset destino y ejecuta baselines completos.
Causa raíz: El mapeo por nombre creó una nueva ruta destino. El antiguo destino aún contiene la ascendencia.
Solución: Renombra el dataset destino para que coincida con el mapeo esperado, o—mejor—cambia a nombres destino estables basados en propiedades.
3) Los backups “tienen éxito”, pero las restauraciones son obsoletas o faltan datos
Síntoma: Los jobs reportan éxito; intentos de restauración encuentran snapshots antiguas.
Causa raíz: La replicación está aterrizando en un dataset distinto al que tu runbook de restauración espera (usualmente por rename/move o múltiples destinos).
Solución: Impone un único destino autorizado por identidad de servicio. Valida “el último snapshot existe en el dataset X”, no “existe en alguna parte”.
4) Receive falla con problemas de mountpoint o filesystem ocupado
Síntoma: zfs recv arroja errores sobre montado, directorios no vacíos o conflictos de mountpoint.
Causa raíz: Propiedades recibidas (o comportamiento de mount por defecto) intentan montar en rutas que existen o están en uso.
Solución: Recibe con -u. En el destino, establece canmount=noauto y un mountpoint seguro (o mountpoint=none) para datasets réplica.
5) La replicación se volvió más lenta con el tiempo y empezó a perder RPO
Síntoma: Mismos datos, mismo enlace, pero las ventanas de replicación se alargan.
Causa raíz: El churn aumentó (logs, tmp, bases), se retienen snapshots más tiempo del previsto, o la fragmentación y presión de espacio aumentaron en el pool.
Solución: Mueve datos de alto churn a su propio dataset con un calendario de snapshots distinto, mantén margen de espacio libre y deja de enviar más frecuentemente de lo que puedes completar.
6) Alguien usó la réplica para “analítica rápida” y ahora los incrementales no aplican
Síntoma: Los incrementales fallan; el receive sugiere rollback/force.
Causa raíz: El destino divergió por escrituras locales.
Solución: Política: los datasets réplica son solo-lectura y no se montan por defecto. Si ya hay divergencia, decide: destruir y reseedear, o usar zfs recv -F solo si aceptas perder cambios del lado destino.
7) El pruning de snapshots rompe la replicación intermitentemente
Síntoma: Algunas ejecuciones funcionan, otras fallan con base ausente.
Causa raíz: La retención borra la snapshot base antes de que el remoto confirme recepción (especialmente tras interrupciones de red).
Solución: Usa holds en snapshots base hasta confirmar replicación. Considera bookmarks como anclas para evitar retención larga de snapshots.
Listas de verificación / plan paso a paso
Checklist A: Hacer la replicación a prueba de renombrados (mapeo por propiedad)
- Elige una raíz destino estable en backup:
bkp/replica. - Para cada dataset de servicio, establece propiedades de identidad en el origen:
org.example:replicate=trueorg.example:replica-name=<service-id>
- En backup, crea datasets destino que coincidan con los service IDs (una vez):
bkp/replica/<service-id>. - Forzar propiedades seguras en objetivos de backup:
canmount=noauto,readonly=on,mountpoint=none(o una ruta segura). - Estandarizar nombres de snapshot:
replica-YYYYMMDD-HHMM. - Implementar holds para la última base replicada; liberar holds tras receive exitoso.
- Agregar un workflow de prueba de restauración usando clones (sin montar la réplica directamente).
Checklist B: Rename o movimiento planificado de dataset (hacerlo sin romper incrementales)
- Confirmar el último snapshot replicado con éxito en ambos lados.
- Colocar un hold en esa snapshot (lado origen) para evitar pruning durante la ventana de cambio.
- Tomar un snapshot pre-cambio (
@replica-pre-rename). - Renombrar/mover el dataset con
zfs rename(y asegurar que los hijos sigan como se espera). - Verificar que las snapshots existan bajo el nuevo nombre de dataset.
- Si mirás nombres en el destino, renombra el dataset destino para coincidir; de lo contrario, asegura que las propiedades aún mapeen al mismo ID destino.
- Reanudar replicación desde el último snapshot común; verificar con una estimación de tamaño dry-run.
- Liberar holds una vez que la nueva snapshot esté confirmada en el receptor.
Checklist C: Cuando debes reseedear (envío completo) de forma segura
- Detener los incrementales para ese dataset.
- Crear un snapshot base fresco en el origen.
- Recibir en una ruta de dataset nueva (temporal) para evitar sobrescribir réplicas conocidas y buenas.
- Tras el éxito, cambiar el mapeo al nuevo dataset (o renombrarlo en su lugar), luego destruir la réplica vieja según la política.
- Reiniciar incrementales desde el snapshot base.
Preguntas frecuentes (FAQ)
1) ¿Renombrar un dataset elimina snapshots o rompe GUIDs de snapshot?
No. Un rename cambia el nombre del dataset en el espacio de nombres. Las snapshots se mueven con él. Lo que rompe es tu automatización si espera la ruta antigua.
2) ¿Por qué la replicación incremental depende tanto de la snapshot base?
Porque un stream incremental es literalmente “aplica cambios desde snapshot A hasta snapshot B.” Si el receptor no tiene snapshot A en el dataset destino, no puede aplicar el delta de forma segura.
3) ¿Debería usar zfs recv -F para arreglar desajustes?
Solo si aceptas que el receptor hará rollback/destruirá snapshots más recientes (y posiblemente cambios locales) para coincidir con el stream.
Es una herramienta, no un valor por defecto. Úsala cuando estés corrigiendo una réplica controlada, no cuando no estés seguro de dónde está aterrizando el stream.
4) ¿Puedo replicar un dataset que fue promovido desde un clone?
Puedes, pero la promoción cambia la ascendencia. Tu base incremental existente puede ya no coincidir.
Espera reseedear o escoger cuidadosamente una nueva cadena común de snapshots.
5) ¿Cómo ayudan los bookmarks con renombrados y pruning?
Los bookmarks no se preocupan por mountpoints y son baratos. Permiten mantener un ancla incremental aunque elimines snapshots antiguas.
No solucionan por sí mismos problemas de mapeo de nombres, pero previenen interrupciones por “snapshot base desaparecida.”
6) ¿Los datasets réplica deberían montarse?
No por defecto. Mantenlos inertes: canmount=noauto y recibe con -u. Para restauraciones, clona un snapshot y monta el clone en una ubicación segura.
7) ¿Replico propiedades como mountpoint y readonly?
Sé selectivo. Replicar mountpoint en un host de backup es una clásica trampa para pies. Preferí establecer propiedades locales seguras en el receptor y evita heredar comportamiento de mount de producción.
8) Si mi dataset origen se mueve bajo otro padre, ¿necesito un envío completo nuevo?
No inherentemente. La ascendencia sigue ahí. Si tu mapeo destino se mantiene estable (ID por propiedad), podés seguir enviando incrementales normalmente.
Si tu mapeo depende del nombre, probablemente crearás una nueva ruta destino y forzarás un reseed accidentalmente.
9) ¿Cómo mantengo navegación amigable para humanos en el backup sin acoplar la replicación a nombres?
Replica en datasets de identidad estables (service IDs). Luego crea clones read-only o datasets secundarios con nombres amigables para navegación.
Los humanos obtienen un árbol; la automatización obtiene estabilidad.
10) ¿Cuál es el mejor hábito para prevenir sorpresas en la replicación?
Pruebas de restauración semanales usando clones, con un runbook que especifique datasets y nombres de snapshot exactos. Expone deriva de mapeo temprano—antes de necesitarlo en un incidente.
Próximos pasos que puedes hacer esta semana
- Elige un esquema de identidad de replicación: mapeo por propiedad a un nombre destino estable por servicio. Implementalo para un dataset primero.
- Haz que los datasets réplica sean inertes: establece
canmount=noauto, recibe con-u, considerareadonly=on. - Estandariza nombres de snapshots y deja de permitir múltiples esquemas de nombres en el mismo entorno.
- Añade holds o bookmarks para que el pruning no pueda borrar la base incremental antes de que la replicación termine.
- Escribe un runbook de prueba de restauración que monte solo clones, nunca el dataset réplica directamente, y ejecútalo según un calendario.
- Instrumenta las señales correctas: último snapshot común por service ID, lag de replicación y “recibido en el dataset esperado”, no solo códigos de salida.
Renombrados y movimientos de datasets son normales. Tu trabajo es hacerlos aburridos. ZFS cooperará—si dejás de tratar los nombres de dataset como destino.