ZFS zfs receive: el lado de importación que falla cuando ignoras las propiedades

¿Te fue útil?

Si zfs send es el departamento de exportación, zfs receive es aduanas: todo parece bien hasta que alguien revisa el papeleo. Las propiedades son ese papeleo. Ignorarlas no solo produce una importación levemente desordenada: obtienes datasets montados donde no deberían estar, claves de cifrado que no puedes cargar, réplicas “exitosas” que no arrancan y un rendimiento que parece un sistema de almacenamiento haciendo danza interpretativa.

He ejecutado replicación ZFS en empresas anodinas, en tiendas SaaS frenéticas y en ese tipo de sótanos corporativos donde “plan DR” significa “rezar”. El patrón se repite: los equipos tratan el receive como un tubo tonto. No lo es. zfs receive es donde ZFS decide qué significa tu datos en el destino: dónde se montará, cómo se comprimirá, si será legible y qué sucede cuando el stream incluye propiedades que no planeaste.

Qué hace realmente zfs receive (y por qué las propiedades son la trampa)

zfs receive toma un stream de replicación y reconstruye datasets, snapshots y, opcionalmente, propiedades. Esa última parte es el asesino silencioso: las propiedades no son solo cosas agradables como “compression=lz4”. Las propiedades incluyen mountpoints, comportamiento de canmount, metadatos de cifrado, cuotas, reservas, recordsize, modo de ACL, almacenamiento de xattr y un montón de cosas específicas de plataforma que pueden no significar lo mismo en el sistema objetivo.

La suposición equivocada es que los streams de replicación son “solo datos”. En realidad, un stream ZFS típico puede incluir:

  • Los bloques de contenido del dataset.
  • Metadatos de snapshots.
  • Propiedades del dataset (dependiendo de los flags de send y la implementación de ZFS).
  • Flags de funcionalidades y expectativas de compatibilidad.

Luego zfs receive aplica el stream en un namespace del pool existente, donde las políticas locales del destino (y los datasets existentes del destino) pueden discrepar con la fuente.

En operaciones, “discrepar” no significa una advertencia amistosa. Significa:

  • Un dataset se monta encima de un directorio real y oculta datos que necesitabas.
  • Una propiedad heredada en la fuente pasa a ser local en el destino (o viceversa) y pasas horas persiguiendo por qué el comportamiento divergió.
  • Un dataset cifrado llega sin el material de claves que esperabas.
  • La replicación “funciona” pero la restauración está rota porque replicaste un mountpoint que solo tiene sentido en el host origen.

Chiste #1: Tratar zfs receive como “solo el lado de importación” es como tratar los airbags como “solo la parte inflable”. Te das cuenta de que estabas equivocado a alta velocidad.

Hechos y contexto histórico que realmente importan en producción

  1. ZFS fue diseñado con la replicación como primitiva de primera clase. Snapshots y send/receive fueron parte de la visión original de la era Sun: transferencia consistente punto en el tiempo sin gymnásticas de congelar el filesystem.
  2. Las propiedades son parte del contrato del filesystem. ZFS usa propiedades para conducir comportamientos que otros sistemas dejan a opciones de montaje o herramientas externas.
  3. OpenZFS divergió entre plataformas durante años. El comportamiento alrededor de ACLs, xattrs y algunos valores por defecto de propiedades puede variar entre illumos, FreeBSD y Linux—especialmente en despliegues antiguos.
  4. Los flags de características cambiaron el riesgo de upgrade. Pools y datasets ganaron flags de funcionalidades que permiten upgrades en caliente y activación más segura, pero también crean sorpresas de “no se puede importar en un sistema más antiguo” si replicas a ciegas a un destino legacy.
  5. El cifrado llegó después que los snapshots. El cifrado nativo es OpenZFS moderno; muchos entornos todavía mezclan “cifrado legado a nivel de disco” (self-encrypting drives, LUKS) con cifrado nativo de ZFS, lo que afecta las suposiciones de replicación.
  6. lz4 se convirtió en la preferencia por defecto de compresión por una razón. Es lo suficientemente rápido como para estar “usualmente activado” sin la vergüenza operativa de desperdiciar CPU en restauraciones.
  7. Existen tokens de reanudación porque las transferencias largas fallan. La replicación ZFS a escala choca con redes inestables, ventanas de mantenimiento e impaciencia humana; los resume tokens convirtieron “empezar de nuevo” en “continuar”.
  8. mountpoint es una propiedad, no una opción de mount(8). Ese diseño es poderoso, pero significa que la replicación puede transportar tu layout de montaje—quer lo quieras o no.
  9. Recordsize y volblocksize son palancas de rendimiento con consecuencias. Pueden hacer que las bases de datos vuelen o que cargas de archivos pequeños sufran, y la replicación puede trasladar esas elecciones entre entornos sin querer.

Un modelo mental práctico: stream, dataset, propiedades y puntos de montaje

Cuando ejecutas:

cr0x@server:~$ zfs send poolA/app@weekly | zfs receive poolB/restore/app

no estás “copiando archivos”. Estás aplicando un registro de transacciones que reconstruye un árbol de datasets. Eso tiene tres capas de consecuencia:

1) Namespace: ¿dónde aterriza el dataset?

poolB/restore/app es el nombre del dataset en el que recibes. Pero dentro del stream, los dataset(s) pueden tener sus propios nombres y propiedades, y si usas flags de replicación que incluyen datasets hijos, el lado receptor puede crear todo un subárbol bajo tu ruta objetivo.

2) Propiedades: ¿qué comportamiento viene de acompañante?

Las propiedades pueden establecerse como valores locales o heredados. La replicación puede preservar esas elecciones. Eso es bueno cuando quieres un espejo fiel y terrible cuando aterrizas datos en un entorno operativo diferente (DR, dev, analytics) con distintos mountpoints, cuotas y ajustes de rendimiento.

3) Comportamiento de montaje: ¿cuándo se monta y dónde?

Por defecto, un filesystem recibido puede montarse automáticamente (dependiendo de canmount, mountpoint y los flags de receive). Eso puede colisionar con rutas y servicios existentes. “¿Por qué mi aplicación lee configuración vieja?” es un tema común en los postmortems.

Comportamiento de las propiedades al recibir: heredadas, locales y “sorpresa, cambió”

La clave para mantener la cordura es separar dos preguntas:

  • ¿Qué propiedades están en el stream? (decidido en el lado del send, y por si haces un raw send, replication send, etc.)
  • ¿Qué propiedades se aplican en el objetivo? (decidido por las opciones de receive y el árbol de datasets existente en el destino)

Propiedades que comúnmente causan interrupciones reales

  • mountpoint: replica una ruta que puede no existir o estar en uso en el destino.
  • canmount: un dataset que no debería montarse se monta (o al revés), cambiando lo que ven los servicios.
  • sharenfs / sharesmb: de repente estás exportando datos en una red que no querías tocar.
  • atime: cambia el churn de metadatos; puede convertir una carga de solo lectura en escrituras inesperadas.
  • recordsize: inofensivo en muchos casos, pero letal para bases de datos e imágenes de VM cuando está mal.
  • xattr y acltype: diferencias entre plataformas pueden hacer que los permisos parezcan “correctos” hasta que una app empiece a fallar.
  • quota/reservation/refquota/refreservation: límites replicados pueden inutilizar una restauración al “llenar” el dataset instantáneamente desde la perspectiva del destino.

Sobrescribir vs excluir propiedades al recibir

Operativamente, normalmente quieres uno de estos patrones:

  • Espejo fiel (mirrór DR que podrías promocionar): preservar propiedades, pero asegurar que el namespace/estrategia de montaje sea segura (a menudo recibir con -u y luego establecer explícitamente los mountpoints).
  • Zona de aterrizaje de datos (analytics/dev/test): sobrescribir mountpoints, deshabilitar shares y aplicar la política local de rendimiento (compression, recordsize) adecuada para el destino.

Si tu ZFS soporta flags para excluir/sobrescribir propiedades en receive (común en OpenZFS moderno), son tus rieles de seguridad. Los flags exactos varían por implementación/versión, así que valídelos en tu entorno con zfs receive -?. En este artículo mostraré patrones que funcionan en general y señalaré dónde importan las diferencias de versión.

Cifrado al recibir: claves, raw sends y el género “¿por qué no puedo montar esto?”

El cifrado nativo de ZFS introduce un concepto que los administradores no cifrados suelen pasar por alto: el dataset puede existir y replicarse perfectamente mientras es inutilizable hasta que se manejen correctamente las claves. Esto no es un bug; es el objetivo.

Dos modos de replicación que se comportan muy diferente

Non-raw send (o sends que efectivamente desencriptan/re-encriptan según la herramienta) pueden permitir que el receptor almacene datos de forma que no sean una réplica byte-por-byte cifrada. Esto puede requerir claves en el origen y puede filtrar propiedades que no pretendías.

Raw send (zfs send -w en muchas compilaciones de OpenZFS) transmite el dataset cifrado como bloques cifrados más metadata suficiente para preservar el cifrado. El receptor no necesita el plaintext, y la raíz/pars del cifrado se preservan. Este es el modo que quieres para “replicar a un sitio DR no confiable” o “appliance de backup”.

El manejo de claves en el destino es un flujo operativo

Recibir un dataset cifrado sin un plan para keylocation y keyformat es como crear el pisapapeles más fiable del mundo. Ejecutarás zfs list, verás los datasets y luego todo fallará en tiempo de montaje.

Chiste #2: Un dataset cifrado sin clave es la forma más pura de almacenamiento de solo escritura. A los auditores les encanta; a los usuarios no.

Puntos de montaje y automontaje: cómo la gente se DOSea con éxito

El escenario más común de “receive rompió producción” no es corrupción. Es una colisión de montaje. Ocurre un receive, el dataset se monta automáticamente y de repente:

  • /var en el destino queda sombreado por un mountpoint replicado.
  • Un directorio con datos activos queda oculto detrás del montaje, haciendo que servicios lean configs obsoletas o no encuentren archivos.
  • Un entorno de restauración comparte datos inesperadamente por NFS/SMB porque llegaron las propiedades de share.

La solución suele ser simple: recibir sin montar, luego establecer explícitamente mountpoints y canmount antes de levantar servicios. Lo difícil es acordarse de hacerlo cuando estás cansado, son las 02:00 y la dirección pide ETAs como si fuera un deporte.

Tareas operativas reales (comandos + interpretación)

A continuación están tareas prácticas que he usado (o visto desear haber usado) en flujos de trabajo de replicación ZFS en producción. Los comandos asumen un entorno OpenZFS típico; ajusta nombres de pool/dataset a tu realidad.

Tarea 1: Inspeccionar qué propiedades importan en la fuente antes de enviar

cr0x@src:~$ zfs get -r -o name,property,value,source mountpoint,canmount,compression,recordsize,quota,refquota,sharenfs,sharesmb poolA/app
NAME             PROPERTY     VALUE          SOURCE
poolA/app        mountpoint   /srv/app       local
poolA/app        canmount     on             default
poolA/app        compression  lz4            local
poolA/app        recordsize   128K           default
poolA/app        quota        none           default
poolA/app        sharenfs     off            default

Interpretación: Esto te dice qué comportamiento estás a punto de replicar. Si mountpoint es local y apunta a una ruta que no existe en el destino, decide ahora si la vas a sobrescribir.

Tarea 2: Ensayar tu plan de receive comprobando el namespace y conflictos en el destino

cr0x@dst:~$ zfs list -o name,mountpoint,canmount -r poolB/restore
NAME                 MOUNTPOINT        CANMOUNT
poolB/restore        /poolB/restore    on
poolB/restore/app    -                 -

Interpretación: Si poolB/restore/app ya existe (o si un dataset padre tiene un mountpoint heredado que causaría una colisión), arregla el namespace antes de recibir.

Tarea 3: Recibir sin montar para evitar colisiones

cr0x@src:~$ zfs send poolA/app@weekly | ssh dst 'zfs receive -u poolB/restore/app'

Interpretación: -u lo deja desmontado incluso si el stream contiene mountpoint/canmount que normalmente lo montarían. Este es el valor por defecto más seguro para restauraciones y semillas de DR.

Tarea 4: Forzar un mountpoint seguro después del receive (patrón landing zone)

cr0x@dst:~$ zfs set mountpoint=/srv/restore/app poolB/restore/app
cr0x@dst:~$ zfs set canmount=noauto poolB/restore/app
cr0x@dst:~$ zfs mount poolB/restore/app

Interpretación: Has hecho el montaje explícito y predecible. noauto evita montajes sorpresa en arranque/import; tú controlas cuándo los servicios ven los datos.

Tarea 5: Recibir un subárbol completo (replicación) en una ruta prefijada

cr0x@src:~$ zfs send -R poolA/app@weekly | ssh dst 'zfs receive -u poolB/restore'

Interpretación: Esto puede crear múltiples datasets bajo poolB/restore. Genial para árboles de aplicación completos; peligroso si no esperabas hijos con sus propios mountpoints y propiedades de share.

Tarea 6: Validar los snapshots recibidos y el uso de espacio

cr0x@dst:~$ zfs list -t snapshot -o name,used,refer,mountpoint -r poolB/restore/app | head
NAME                              USED  REFER  MOUNTPOINT
poolB/restore/app@weekly          0B    220G   /srv/restore/app
poolB/restore/app@daily-2025-12   0B    218G   /srv/restore/app

Interpretación: Snapshots presentes, tamaños refer parecen razonables. Si USED es masivo inesperadamente, puedes haber recibido snapshots extra o incrementales desajustados.

Tarea 7: Send/receive incremental con snapshot base claro

cr0x@src:~$ zfs send -I poolA/app@weekly poolA/app@daily | ssh dst 'zfs receive -u poolB/restore/app'

Interpretación: -I envía todos los snapshots intermedios entre weekly y daily. Si el destino no tiene el snapshot base, esto fallará. Ese fallo es saludable—evita divergencia silenciosa.

Tarea 8: Manejar un dataset que ya existe en el destino (force receive)

cr0x@src:~$ zfs send poolA/app@weekly | ssh dst 'zfs receive -F -u poolB/restore/app'

Interpretación: -F revierte el dataset destino al snapshot más reciente que coincida con el stream y descarta cambios divergentes. Esto es poderoso y peligroso: si alguien escribió datos en el destino, se pierden.

Tarea 9: Usar resume tokens cuando una receive larga se interrumpe

cr0x@dst:~$ zfs get -H -o value receive_resume_token poolB/restore/app
1-2f4c9e1b7a-8000000000-1a2b3c4d5e6f...

cr0x@dst:~$ zfs send -t 1-2f4c9e1b7a-8000000000-1a2b3c4d5e6f... | zfs receive -u poolB/restore/app

Interpretación: Si tu entorno soporta resume tokens, puedes continuar sin reenviar desde cero. Si el token es - o vacío, reanudar no está disponible o no está activo.

Tarea 10: Confirmar estado de cifrado y requisitos de clave después del receive

cr0x@dst:~$ zfs get -o name,property,value encryption,keylocation,keystatus -r poolB/restore/app
NAME                 PROPERTY     VALUE        SOURCE
poolB/restore/app    encryption   aes-256-gcm  -
poolB/restore/app    keylocation  file:///...  local
poolB/restore/app    keystatus    unavailable  -

Interpretación: El dataset existe pero las claves no están cargadas. No puedes montarlo hasta cargar las claves. Si keylocation es incorrecto para el destino, arréglalo antes de intentar montarlo.

Tarea 11: Cargar claves y montar de forma segura (datasets cifrados)

cr0x@dst:~$ zfs set keylocation=prompt poolB/restore/app
cr0x@dst:~$ zfs load-key poolB/restore/app
Enter passphrase for 'poolB/restore/app': 
cr0x@dst:~$ zfs mount poolB/restore/app

Interpretación: Cambiar a prompt es una elección operativa común para restauraciones DR. Para arranques desatendidos podrías usar un keylocation basado en archivo, pero eso es otra conversación de seguridad que debes tener intencionalmente.

Tarea 12: Medir si el receive está limitado por CPU, disco o red

cr0x@dst:~$ zpool iostat -v 1
                              capacity     operations     bandwidth
poolB                         alloc   free   read  write   read  write
----------------------------  -----  -----  -----  -----  -----  -----
poolB                          8.2T  10.1T      0   1200     0   450M
  raidz2-0                     8.2T  10.1T      0   1200     0   450M
    sda                            -      -      0    150     0    58M
    sdb                            -      -      0    148     0    57M

Interpretación: Si las escrituras de disco son altas y constantes pero el receive es lento, puedes estar limitado por CPU en descompresión/checksum, o por ajustes de sync. Si las escrituras son bajas, mira la red/SSH o el lado del send.

Tarea 13: Detectar colisiones de mountpoint antes de que te muerdan

cr0x@dst:~$ zfs get -r -o name,property,value,source mountpoint,canmount poolB/restore | grep -E '(/var|/usr|/home|/srv)'
poolB/restore/app    mountpoint  /srv/app     local

Interpretación: Ese mountpoint es un problema si /srv/app es donde vive la app de producción real en el destino. Arréglalo antes de montar.

Tarea 14: Verificar qué cambió durante el receive comparando fuentes de propiedades

cr0x@dst:~$ zfs get -o name,property,value,source -s local,inherited compression,recordsize,atime,acltype,xattr poolB/restore/app
NAME                 PROPERTY     VALUE   SOURCE
poolB/restore/app    compression  lz4     local
poolB/restore/app    atime        off     inherited
poolB/restore/app    xattr        sa      local

Interpretación: Así detectas problemas de “es diferente en DR” sin adivinanzas. Si una propiedad está heredada de un padre en el destino, quizás necesites establecerla localmente para coincidir con las expectativas.

Guión de diagnóstico rápido (qué revisar primero, segundo, tercero)

Esta es la lista que uso cuando la replicación es lenta, está rota o “exitosa pero incorrecta”. El objetivo es identificar la capa limitante en minutos, no horas.

Primero: ¿es el estado del dataset incorrecto (propiedades/montaje/cifrado)?

cr0x@dst:~$ zfs list -o name,type,mountpoint,canmount -r poolB/restore/app
cr0x@dst:~$ zfs get -o name,property,value,source mountpoint,canmount,readonly,sharenfs,sharesmb poolB/restore/app
cr0x@dst:~$ zfs get -o name,property,value encryption,keystatus,keylocation poolB/restore/app

Interpretación: Si no se monta, el cifrado y el mountpoint son sospechosos principales. Si se montó en un lugar inesperado, mountpoint/canmount y propiedades heredadas suelen ser la causa.

Segundo: ¿la relación del stream es incorrecta (falta base incremental, necesita rollback, resume token)?

cr0x@dst:~$ zfs list -t snapshot -o name -r poolB/restore/app | tail
cr0x@dst:~$ zfs get -H -o value receive_resume_token poolB/restore/app

Interpretación: Las fallas incrementales suelen reducirse a “el destino no tiene el snapshot base exacto” o “el destino ha divergido”. La presencia del resume token te dice si puedes continuar.

Tercero: ¿dónde está el cuello de botella (disco, CPU, red, sync)?

cr0x@dst:~$ zpool iostat 1
cr0x@dst:~$ iostat -xz 1
cr0x@dst:~$ vmstat 1

Interpretación: Buscas un recurso saturado: discos al 100%, CPU al 100% (a menudo checksum/compresión/descifrado) o una tubería lenta por SSH/red. Si las escrituras se detienen con alta latencia, revisa la salud del pool y el desajuste recordsize/workload.

Errores comunes: síntomas específicos y soluciones

Error 1: Recibir en una ruta que se auto-monta sobre datos reales

Síntomas: Los servicios “pierden” archivos, la config desaparece, los directorios parecen vacíos o aparece datos antiguos de repente. mount muestra un nuevo filesystem ZFS montado en una ruta crítica.

Solución: Desmonta inmediatamente el dataset recibido y establece un mountpoint seguro.

cr0x@dst:~$ zfs unmount poolB/restore/app
cr0x@dst:~$ zfs set mountpoint=/srv/restore/app poolB/restore/app
cr0x@dst:~$ zfs set canmount=noauto poolB/restore/app

Error 2: Receive incremental falla con “does not exist” o “most recent snapshot does not match”

Síntomas: Receive falla; la lista de snapshots del destino no contiene la base incremental; o el destino tiene snapshots extra que no están en la fuente.

Solución: Re-seed con el snapshot base correcto, o forzar rollback si quieres sobrescribir la divergencia intencionalmente.

cr0x@src:~$ zfs send poolA/app@weekly | ssh dst 'zfs receive -u poolB/restore/app'
cr0x@src:~$ zfs send -i poolA/app@weekly poolA/app@daily | ssh dst 'zfs receive -u poolB/restore/app'

Si existe divergencia y los cambios en el destino son desechables:

cr0x@src:~$ zfs send -i poolA/app@weekly poolA/app@daily | ssh dst 'zfs receive -F -u poolB/restore/app'

Error 3: Cuotas/reservas se replican en DR y bloquean restauraciones

Síntomas: Recibes con éxito, pero las escrituras fallan inmediatamente con “out of space” a pesar de tener espacio libre en el pool. zfs get quota muestra un valor restrictivo.

Solución: Anula cuotas/reservas en el destino después del receive (o exclúyelas durante el receive si está soportado).

cr0x@dst:~$ zfs get -o name,property,value quota,refquota,reservation,refreservation poolB/restore/app
cr0x@dst:~$ zfs set quota=none refquota=none reservation=none refreservation=none poolB/restore/app

Error 4: Recibir datasets cifrados sin plan para keylocation/keystatus

Síntomas: El dataset existe; los montajes fallan; keystatus=unavailable.

Solución: Establece el keylocation correcto y carga las claves, luego monta.

cr0x@dst:~$ zfs set keylocation=prompt poolB/restore/app
cr0x@dst:~$ zfs load-key poolB/restore/app
cr0x@dst:~$ zfs mount poolB/restore/app

Error 5: “Optimización” cambiando recordsize/compression en el destino a mitad de stream

Síntomas: El receive se vuelve más lento; la fragmentación aumenta; el rendimiento de la aplicación cambia impredeciblemente tras la promoción.

Solución: Trata las propiedades de rendimiento como parte del diseño del sistema. Decide la política por rol de dataset (DB vs logs vs imágenes VM) y configúrala de forma consistente—preferiblemente antes de la primera ingestión o con ventanas de reescritura controladas.

cr0x@dst:~$ zfs set recordsize=16K poolB/restore/db
cr0x@dst:~$ zfs set compression=lz4 poolB/restore/db

Error 6: Replicar accidentalmente propiedades de share a la zona de red equivocada

Síntomas: Los datos aparecen en la red inesperadamente; el equipo de cumplimiento llama; los logs del firewall se encienden.

Solución: Asegura que sharenfs/sharesmb estén off en los destinos de receive (o heredados como off desde un dataset padre).

cr0x@dst:~$ zfs set sharenfs=off sharesmb=off poolB/restore
cr0x@dst:~$ zfs inherit -r sharenfs poolB/restore/app
cr0x@dst:~$ zfs inherit -r sharesmb poolB/restore/app

Listas de verificación / plan paso a paso

Plan A: Restauración segura en un entorno nuevo (predeterminado recomendado)

  1. Crear un dataset padre de aterrizaje con propiedades heredadas seguras (sin shares, sin sorpresas de auto-montaje).
  2. Recibir con -u para evitar el auto-montaje.
  3. Sobrescribir mountpoints hacia un prefijo de restauración.
  4. Manejar claves de cifrado explícitamente (política prompt o basada en archivos).
  5. Verificar snapshots y fuentes de propiedades antes de exponer los datos a los servicios.
cr0x@dst:~$ zfs create -o canmount=off -o mountpoint=/srv/restore poolB/restore
cr0x@dst:~$ zfs set sharenfs=off sharesmb=off poolB/restore
cr0x@src:~$ zfs send -R poolA/app@weekly | ssh dst 'zfs receive -u poolB/restore'
cr0x@dst:~$ zfs set mountpoint=/srv/restore/app poolB/restore/app
cr0x@dst:~$ zfs set canmount=noauto poolB/restore/app

Plan B: Espejo DR que podrías promocionar (fiel, pero controlado)

  1. Recibir desmontado.
  2. Preservar propiedades a menos que tengas una excepción de política.
  3. Mantener mountpoints “seguros para DR” recibiendo bajo una raíz dedicada y solo cambiar mountpoints en el momento de la promoción.
  4. Probar la promoción en ensayos (montar, cargar claves, iniciar servicios) con runbooks que asuman que los humanos cometen errores bajo estrés.

Plan C: Replicación a largo plazo sobre enlaces inestables

  1. Preferir receive reanudable donde se soporte.
  2. Rastrear resume tokens y evitar matar receives a menos que lo quieras.
  3. Medir cuellos de botella con zpool iostat y métricas de CPU del sistema antes de “tunar”.

Tres mini-historias del mundo corporativo

Mini-historia 1: El incidente causado por una suposición equivocada (mountpoints son “solo locales”)

Un equipo de infra corporativa construyó un nuevo entorno DR. El plan parecía sólido: replicar los datasets de producción cada noche, mantenerlos offline y en un desastre simplemente “montar y listo”. Probaron la mecánica de replicación—los streams fluyeron, aparecieron snapshots y los gráficos de almacenamiento lucían tranquilos. Todos se fueron temprano a casa, lo cual siempre es sospechoso.

Meses después, un ensayo rutinario de DR se convirtió en un incidente de producción, que es la forma más eficiente de ensayo. Un ingeniero junior hizo un receive en un pool que también alojaba un entorno de staging. El dataset llegó con mountpoint=/var/lib/app heredado de producción. En el host DR, ese directorio existía y tenía datos de staging. El receive montó automáticamente y ocultó el directorio de staging. Los servicios no fallaron de inmediato; empezaron a leer silenciosamente archivos replicados más antiguos que resultaban parecer válidos.

La primera reacción del equipo fue depurar la aplicación. Vieron logs, reiniciaron contenedores, culparon al DNS y miraron dashboards hasta que alguien finalmente ejecutó mount y vio un filesystem ZFS encima de una ruta que nunca debía tocarse.

La solución tomó minutos: desmontar, recibir con -u, establecer un mountpoint seguro y luego montar explícitamente. El postmortem tomó más tiempo, porque la suposición equivocada (“mountpoint es configuración local”) se había institucionalizado en scripts y conocimiento tribal. La acción correctiva real fue cultural: tratar las propiedades como parte del estado replicado y siempre aterrizar restauraciones en un namespace que no pueda colisionar con rutas activas.

Mini-historia 2: La optimización que salió mal (cambiar perillas en vuelo)

Otra organización tenía una canalización de replicación “demasiado lenta”. Los incrementales nocturnos ocasionalmente se extendían a horas laborables, lo que ofendía a un VP que gustaba de líneas claras en los gráficos. El equipo de almacenamiento decidió acelerar cambiando compresión y recordsize en el destino, argumentando que “receive solo escribe bloques; las propiedades del destino lo harán más rápido”.

Establecieron compresión agresiva y ajustaron recordsize para datasets que contenían imágenes VM. En papel, bloques más pequeños y compresión más fuerte sonaban a menos IO y transferencias más rápidas. En la práctica, el lado receptor se volvió CPU-bound. Los nodos de destino no estaban dimensionados para trabajo intenso de compresión/descompresión, y la ventana de replicación empeoró. Mientras tanto, los datasets de imágenes VM comenzaron a fragmentarse de manera que hicieron las promociones posteriores lentas e impredecibles.

Lo mejor: como los receives seguían “exitosos”, nadie notó de inmediato. El dolor apareció semanas después durante una prueba de failover planificada, cuando los boot storms tardaron más y la latencia de almacenamiento se disparó. El equipo había optimizado la canalización de replicación, pero degradó accidentalmente la carga operativa que realmente importaba.

La recuperación fue aburrida: volver a lz4, alinear recordsize con la realidad de la carga de trabajo y separar los objetivos. Si quieres replicación más rápida, aborda la canalización (red, cifrado SSH, flags de send, concurrencia) y el diseño del pool (layout de vdev, política de sync), no cambiando semánticas de dataset al azar en mitad de la replicación.

Mini-historia 3: La práctica aburrida pero correcta que salvó el día (recibir desmontado, luego promocionar deliberadamente)

La tienda ZFS más resiliente que he visto no tenía el hardware más lujoso. Tenía la disciplina de replicación más aburrida: cada receive aterrizaba desmontado bajo un root dedicado con “defaults seguros”. Sin shares. Sin automount. Nombres claros. Y cada promoción tenía un runbook con chequeos de propiedades explícitos.

Durante un incidente real—problemas en el controlador de almacenamiento en la primaria—el equipo decidió promover DR. La presión era real, pero los pasos fueron mecánicos: cargar claves de cifrado, verificar presencia de snapshots, establecer mountpoints a las rutas de producción, cambiar canmount, montar e iniciar servicios. No “montaron todo y vieron qué pasa”, que es el equivalente en almacenamiento a escalar sin cuerda.

Aún tuvieron problemas, porque todos los tienen. Un dataset tenía una cuota obsoleta replicada de un experimento de capacidad de hace tiempo. Pero como siempre ejecutaban una lista de diff de propiedades antes de la promoción, la detectaron antes de que la aplicación empezara a escribir. Limpiaron la cuota, montaron y siguieron.

La lección no fue heroica. Fue que el proceso aburrido escala bajo estrés. Un runbook DR que suponga las minas de propiedades vale más que mil líneas de scripts de replicación ingeniosos.

Preguntas frecuentes

1) ¿Por qué a zfs receive le importan las propiedades?

Porque en ZFS, las propiedades definen comportamientos que otros sistemas de archivos externalizan. Replicar datos sin comportamiento suele ser una restauración rota: mountpoints equivocados, perfil de rendimiento incorrecto y modelo de permisos erróneo.

2) ¿Debería preservar siempre las propiedades al replicar?

No. Preserva propiedades para un espejo verdadero que puedas promocionar. Sobrescribe/excluye propiedades para zonas de aterrizaje (dev/analytics/forensics) donde el layout de mount, shares y cuotas de la fuente no aplican.

3) ¿Cuál es el flag de receive más seguro por defecto en un entorno desconocido?

-u (no montar). Previene la clase más común de outages auto-infligidos: colisiones de mountpoint e interacciones sorpresa con servicios.

4) ¿Cuándo debo usar zfs receive -F?

Cuando quieres sobrescribir el estado divergente del destino y aceptas perder cambios hechos en el destino desde el snapshot común. Es apropiado para mirrors DR donde el destino no es una primaria escribible.

5) ¿Por qué mi receive incremental falló aunque el dataset exista?

Los incrementales dependen del grafo de snapshots. El destino debe tener el snapshot base exacto (o la cadena de snapshots) referenciada por el stream. Si el destino divergió o se podó el snapshot base, receive falla para evitar corrupción por suposición.

6) ¿Cómo afectan las claves de cifrado a la replicación?

Los datasets cifrados pueden replicarse cifrados (raw) o en un modo que implica plaintext en el sender. En el receptor, el dataset puede existir pero quedar no montable hasta cargar claves. Siempre valida keystatus y decide cómo debería comportarse keylocation en DR.

7) ¿Por qué mi receive es lento?

La mayoría de receives lentos están limitados por uno de: throughput/latencia de escritura en disco, CPU (checksum/compresión/descifrado), overhead de red/SSH o settings de escritura sincrónica. Mide con zpool iostat, iostat y métricas de CPU antes de cambiar propiedades de dataset.

8) ¿Puedo replicar de un OS a otro con seguridad?

Usualmente sí, pero vigila propiedades relacionadas con ACL y xattr, flags de funcionalidades y comportamientos por defecto. Trata la replicación cross-platform como un proyecto de compatibilidad: valida la semántica de permisos con tests reales de la aplicación, no solo con ls -l.

9) ¿Está bien cambiar mountpoints después del receive?

Sí, y a menudo es la decisión correcta. Recibe en un namespace seguro y luego establece mountpoints como parte de la promoción. Esto mantiene la replicación consistente mecánicamente a la vez que previene colisiones.

10) ¿Cómo evito replicar propiedades “peligrosas” como shares?

Establece defaults heredados seguros en el dataset padre en el destino (shares off), recibe desmontado y luego configura explícitamente cualquier compartición que realmente quieras. Si tu ZFS soporta excluir propiedades en receive, úsalo—pero mantén el patrón de seguridad del padre como red de seguridad.

Conclusión

zfs receive no es un endpoint pasivo. Es donde tus datos replicados se convierten en un filesystem real con comportamientos reales, y esos comportamientos están gobernados por propiedades—algunas obvias, otras sutiles y otras que solo aparecen cuando restauras bajo presión.

Si tomas una regla operativa de esto: recibe desmontado en un namespace seguro, luego aplica una política deliberada de propiedades antes de montar o compartir nada. Ese único hábito previene la clásica interrupción por colisión de mountpoint, hace que las restauraciones cifradas sean predecibles y convierte la replicación de un ritual esperanzado en un sistema de ingeniería en que puedes confiar.

← Anterior
L2ARC de ZFS en SSD SATA: cuándo merece la pena
Siguiente →
Debian 13: Sistema de archivos montado como solo lectura tras errores — pasos de recuperación que minimizan el daño

Deja un comentario