Herencia de propiedades ZFS: la sorpresa que cambia datasets hijos

¿Te fue útil?

Los incidentes más costosos en ZFS rara vez comienzan con fallos de disco. Empiezan con confianza.
Alguien cambia un dataset “solo para arreglar esto”, y una semana después la mitad del parque está
más lento, los trabajos de backup explotan o falta un montaje al arrancar. Nada parece “roto” de forma
obvia. ZFS sigue sano. El pool está ONLINE. Y sin embargo el sistema se comporta de forma diferente.

El culpable suele ser la herencia de propiedades de ZFS: la funcionalidad que adoras cuando escalas
limpiamente—y la funcionalidad que hace que un dataset hijo cambie silenciosamente cuando no lo pretendías.
Si gestionas producción, trata la herencia como tablas de enrutamiento: potente, de impacto global,
y no algo que “simplemente pruebas”.

La herencia es la característica y la trampa

Los datasets de ZFS (filesystems y volúmenes) viven en un árbol. Las propiedades residen en nodos de ese árbol.
Algunas propiedades se heredan hacia abajo en el árbol a menos que sean anuladas. Eso es intencional: te permite
aplicar una política en un dataset padre y que todos los hijos la sigan.

La trampa es humana: nosotros pensamos en “este dataset” y ZFS piensa en “este subárbol”. Ajustas una
propiedad en tank/apps para resolver un problema de una aplicación, y acabas de cambiar el comportamiento de
tank/apps/postgres, tank/apps/redis y del olvidado tank/apps/tmp que un runner de CI ha estado usando durante dos años.

La herencia no es solo sobre “buenos valores por defecto”. Puede cambiar el comportamiento de montaje, el rendimiento, la visibilidad de snapshots,
la semántica de acceso, el manejo de claves de cifrado y las garantías de espacio. Eso significa que la herencia puede:

  • arreglar cosas rápidamente cuando se usa de forma deliberada,
  • provocar incidentes de “nadie tocó ese dataset” cuando se usa de manera casual,
  • crear deriva de configuración que resiste la depuración (“¿se hereda desde dónde?”).

Una regla operativa que me gusta: si no puedes nombrar los datasets padre que se verán impactados por tu cambio,
no estás listo para ejecutar zfs set.

Broma #1: La herencia de ZFS es como el Wi‑Fi de la oficina: todos se benefician hasta que alguien “lo optimiza” y todo el piso
empieza a hacer buffering.

El único modelo mental que escala: local vs inherited vs default

Cuando consultas una propiedad, ZFS puede mostrar el valor y la fuente. La fuente es lo que te salva:

  • local: establecido explícitamente en ese dataset
  • inherited from <dataset>: heredado de un ancestro
  • default: valor por defecto compilado para tu implementación/version de ZFS
  • temporary: establecido en tiempo de ejecución (menos común; piensa en opciones de montaje en algunas pilas)

Aquí está por qué esto importa en producción: dos datasets pueden mostrar el mismo valor pero comportarse diferente
operativamente porque la fuente te dice qué ocurrirá en la próxima ventana de cambios.
Un valor por defecto permanece estable hasta que lo cambias. Un valor heredado puede cambiar cuando alguien modifica el padre.
Un valor local es “pegajoso” pero puede quedarse obsoleto respecto a la política.

Reglas de herencia que importan en la vida real

  • La mayoría de las propiedades son heredables; no todas. ZFS tiene propiedades que son de solo lectura o
    inherentemente locales (por ejemplo, used, available).
  • Los hijos heredan a menos que haya una anulación local. Si un hijo tiene un valor local, los cambios del padre
    no lo afectarán.
  • zfs inherit elimina la anulación local. No “pone al valor por defecto”. Elimina la
    configuración local y el valor pasa a heredarse (o a default, si no hay nada que herede).
  • Los clones complican la historia. Los clones nacen como hijos de un snapshot de origen; todavía
    existen en el árbol de datasets y por tanto heredan de padres como cualquier otro.
  • Las propiedades relacionadas con montaje pueden fallar de formas que parecen problemas de systemd. No lo son. Son
    ZFS, negándose educadamente a montar lo que le dijiste que montara.

Una sola cita que puedes usar con tu equipo de almacenamiento

W. Edwards Deming (idea parafraseada): “Un sistema está perfectamente diseñado para obtener los resultados que obtiene.”
Las sorpresas por herencia son tu sistema funcionando exactamente como fue diseñado.

Propiedades que muerden fuerte (y por qué)

1) mountpoint, canmount, y readonly: el trío “¿dónde se fue mi filesystem?”

Si gestionas servidores, ya habrás depurado “se montó ayer” al menos una vez. El comportamiento de montaje de ZFS está guiado por propiedades.
Un dataset padre con canmount=off puede ser un límite administrativo limpio—hasta que se aplica en el nodo equivocado y los hijos dejan de montarse al arrancar.

mountpoint se hereda por defecto, lo cual es conveniente y peligroso. Si pones el mountpoint del padre en /srv,
podrías esperar que solo el padre se mueva. En su lugar, los hijos se montan bajo él a menos que tengan mountpoints locales. Genial cuando está planeado; caos cuando no.

2) compression: pequeño interruptor, gran radio de impacto

La compresión es heredable. Eso es una característica: puedes activar compression=lz4 en un padre y
beneficiarte ampliamente. La sorpresa: también puedes apagarla para un subárbol y aumentar silenciosamente la capacidad.
Peor aún, puedes cambiarla en un dataset que incluye “base de datos caliente” y “logs fríos” y ahora tu afinación es
un compromiso que no pretendías.

3) recordsize: tuning de rendimiento que se convierte en regresión

recordsize es heredable en filesystems. Ponlo mal en un padre y tus hijos heredan un tamaño de registro que puede ser terrible para su carga.
Un directorio de datos de PostgreSQL normalmente prefiere bloques pequeños para IO aleatorio; un archivo de medios prefiere bloques grandes.
Tu árbol de datasets no debería obligarlos a usar la misma “talla de ropa”.

4) atime: muerte por un millón de escrituras pequeñas

Las actualizaciones de tiempo de acceso (atime=on) pueden generar una escritura en lecturas. En datasets concurridos y con muchas lecturas
(buzones de correo, caches web, árboles con mucho metadata), atime=on heredado puede convertir “las lecturas son baratas”
en “las lecturas también son escrituras”, lo que afecta latencia y desgaste.

5) snapdir y visibilidad de snapshots

snapdir=visible hace que .zfs/snapshot aparezca. A veces es genial para restauraciones self-service,
y otras un riesgo operativo o de cumplimiento cuando aplicaciones lo recorren. La herencia significa que puedes exponer
snapshots en lugares que no pretendías. O esconderlos donde dependías de ellos operativamente.

6) Cuotas, reservaciones, refreservations: “espacio” es política

Las cuotas y reservas pueden heredarse dependiendo del tipo de propiedad y tu diseño. Incluso cuando no se heredan,
su interacción con la asignación padre/hijo es donde vive la sorpresa. La gestión de espacio no es intuitiva bajo presión.
Puedes acabar con espacio libre a nivel de pool pero “no queda espacio” para un dataset por reservas, o con un hijo que crece sin límite
porque asumiste que una cuota en el padre lo cubriría.

7) Propiedades de cifrado: herencia con aristas afiladas

El cifrado nativo de ZFS introduce propiedades heredables como encryption, keylocation,
keyformat y más. El comportamiento central: los hijos pueden heredar configuraciones de cifrado del padre al crearse.
Pero cambiar el cifrado no es como cambiar la compresión; no puedes simplemente activarlo para un dataset existente sin
una migración send/receive. La gente todavía lo intenta. La “sorpresa” aquí suele ser un fallo de proceso: esperar que la herencia
asegure datos retroactivamente.

Broma #2: Cambiar recordsize en el dataset padre es como comprarle a toda la compañía la misma silla—alguien va a abrir un ticket por dolor de espalda.

Datos interesantes y contexto histórico

  • ZFS fue diseñado para sustituir una pila, no solo un filesystem. Combinó gestión de volúmenes y semántica de sistema de archivos, así que las “propiedades” se convirtieron en perillas de política del sistema.
  • Las propiedades se construyeron para delegación. Despliegues tempranos de ZFS necesitaban una forma de permitir que los equipos se autogestionaran datasets de forma segura; la herencia fue el mecanismo de escala.
  • Las elecciones por defecto de compresión evolucionaron. Muchas pilas modernas recomiendan lz4 ampliamente; las guías antiguas eran más cautelosas porque la CPU era más escasa y los algoritmos diferían.
  • recordsize vino de un mundo de IO secuencial grande. ZFS optimizó para cargas de streaming; las bases de datos obligaron a la comunidad a disciplinarse sobre el tuning por dataset.
  • Los valores por defecto de atime reflejan la tradición Unix, no la realidad de rendimiento. Los tiempos de acceso eran útiles para herramientas y políticas, pero los sistemas modernos a menudo pagan demasiado por ellos.
  • El comportamiento de montaje solía ser más “mágico”. Integraciones OS diferentes (herencia Solaris vs ZFS en Linux) moldearon cómo las propiedades interactúan con el arranque y los servicios de montaje.
  • La visibilidad de snapshots es una guerra cultural. Los admins aman .zfs/snapshot para restauraciones; los dueños de apps odian directorios sorpresa. La propiedad existe porque ambos tienen razón.
  • Las fuentes de propiedad fueron un regalo de depuración agregado temprano. “local vs inherited vs default” es una de las mejores decisiones UX orientadas al operador en herramientas de almacenamiento.

Tareas prácticas (comandos, salidas, decisiones)

Estos son los chequeos que ejecuto cuando alguien dice “ZFS cambió algo”. Cada tarea incluye: el comando,
qué significa la salida y la decisión que tomas a continuación.

Task 1: Ver el valor de la propiedad y de dónde viene

cr0x@server:~$ zfs get -o name,property,value,source compression tank/apps/postgres
NAME                PROPERTY     VALUE  SOURCE
tank/apps/postgres  compression  lz4    inherited from tank/apps

Significado: El dataset Postgres está comprimido porque su padre lo está. Si cambias tank/apps,
cambias Postgres. Decisión: si Postgres necesita un comportamiento especial, establece una anulación local en
tank/apps/postgres y deja de depender del padre para esa propiedad.

Task 2: Auditar un subárbol por una propiedad (captura deriva rápido)

cr0x@server:~$ zfs get -r -o name,property,value,source recordsize tank/apps
NAME                 PROPERTY    VALUE   SOURCE
tank/apps             recordsize  128K   local
tank/apps/postgres    recordsize  128K   inherited from tank/apps
tank/apps/redis       recordsize  128K   inherited from tank/apps
tank/apps/artifacts   recordsize  128K   inherited from tank/apps

Significado: Una configuración local en tank/apps dicta todo el subárbol.
Decisión: acepta eso como política, o separa datasets especiales con tamaños de record locales
(por ejemplo, Postgres a 16K o 8K dependiendo de tu almacenamiento y carga).

Task 3: Encontrar todo lo que hereda de un padre específico (radio de impacto)

cr0x@server:~$ zfs get -r -H -o name,source mountpoint tank/apps | awk '$2 ~ /tank\/apps/ {print $1, $2}'
tank/apps/postgres inherited from tank/apps
tank/apps/redis inherited from tank/apps
tank/apps/artifacts inherited from tank/apps

Significado: Esos datasets se moverán si cambias el mountpoint de tank/apps.
Decisión: no toques mountpoint en el padre a menos que tengas una ventana de mantenimiento
y un plan de rollback.

Task 4: Verificar qué se montará y qué no

cr0x@server:~$ zfs get -r -o name,canmount,mountpoint,mounted tank/apps
NAME                CANMOUNT  MOUNTPOINT            MOUNTED
tank/apps            on       /srv/apps             yes
tank/apps/postgres   on       /srv/apps/postgres    yes
tank/apps/redis      on       /srv/apps/redis       yes

Significado: Todos los datasets son elegibles para montarse y están montados.
Decisión: si falta algo, busca canmount=off o mountpoint=none
y traza de dónde se hereda.

Task 5: Detectar “cambiamos el mountpoint del padre” antes de que el reboot lo haga

cr0x@server:~$ zfs set mountpoint=/srv tank/apps
cr0x@server:~$ zfs mount -a
cannot mount 'tank/apps/postgres': mountpoint or dataset is busy

Significado: El nuevo layout de mounts entra en conflicto con montajes existentes o procesos en ejecución.
Decisión: revierte el mountpoint del padre inmediatamente, o detén servicios limpiamente y vuelve a montar
en una ventana controlada. No “forces” en caliente.

Task 6: Identificar anulaciones locales (los lugares donde la política no llega)

cr0x@server:~$ zfs get -r -H -o name,property,value,source compression tank | awk '$4=="local"{print}'
tank/apps            compression  lz4  local
tank/backups         compression  off  local

Significado: Solo dos datasets tienen configuraciones explícitas de compresión; todo lo demás es heredado o por defecto.
Decisión: confirma que estas anulaciones locales son intencionales. Las anulaciones locales son donde las “normas”
se detienen silenciosamente.

Task 7: Restablecer un dataset hijo para que herede de nuevo (despersonalización controlada)

cr0x@server:~$ zfs get -o name,property,value,source atime tank/apps/postgres
NAME                PROPERTY  VALUE  SOURCE
tank/apps/postgres  atime     off    local
cr0x@server:~$ zfs inherit atime tank/apps/postgres
cr0x@server:~$ zfs get -o name,property,value,source atime tank/apps/postgres
NAME                PROPERTY  VALUE  SOURCE
tank/apps/postgres  atime     on     inherited from tank/apps

Significado: Eliminaste la anulación local, y ahora Postgres sigue al padre.
Decisión: haz esto solo si estás seguro de que la política del padre es correcta para la carga. “Inherit”
no es un botón de limpieza gratuito.

Task 8: Ver qué propiedades están establecidas localmente en un dataset (perfil rápido)

cr0x@server:~$ zfs get -s local all tank/apps/postgres | head
NAME                PROPERTY          VALUE                  SOURCE
tank/apps/postgres  atime             off                    local
tank/apps/postgres  logbias           latency                local
tank/apps/postgres  primarycache      metadata               local
tank/apps/postgres  recordsize        16K                    local

Significado: Estas son desviaciones deliberadas de la política heredada/por defecto.
Decisión: si no esperabas esto, tienes deriva de configuración. Rastrea quién lo puso y por qué.
Si lo esperabas, documéntalo porque alguien intentará “limpiarlo” más tarde.

Task 9: Usar zfs diff para entender cambios visibles por snapshot (chequeo de la realidad ops)

cr0x@server:~$ zfs diff tank/apps/postgres@before-change tank/apps/postgres@after-change | head
M       /srv/apps/postgres/postgresql.conf
+       /srv/apps/postgres/pg_wal/0000000100000000000000A1
-       /srv/apps/postgres/tmp/old.sock

Significado: Esto es rastreo a nivel de archivo entre snapshots, no cambios de propiedad.
Decisión: si cambió el rendimiento pero los datos no, probablemente estás viendo herencia de propiedades o
comportamiento del kernel/módulo—no “alguien editó archivos”.

Task 10: Confirmar el layout de datasets y detectar hijos que no deberían existir

cr0x@server:~$ zfs list -r -o name,used,avail,refer,mountpoint tank/apps
NAME                  USED  AVAIL  REFER  MOUNTPOINT
tank/apps             120G   2.1T   256K  /srv/apps
tank/apps/postgres     80G   2.1T    80G  /srv/apps/postgres
tank/apps/redis         2G   2.1T     2G  /srv/apps/redis
tank/apps/tmp          35G   2.1T    35G  /srv/apps/tmp

Significado: Un dataset tmp pesado dentro del subárbol apps es un peligro clásico de herencia.
Decisión: mueve cargas efímeras a su propio dominio de política padre (o establece anulaciones locales),
especialmente para recordsize, sync y atime.

Task 11: Comprobar si cuotas o reservas están limitando silenciosamente a los hijos

cr0x@server:~$ zfs get -r -o name,quota,reservation,refreservation,used,available tank/apps/postgres
NAME                QUOTA  RESERVATION  REFRESERVATION  USED  AVAILABLE
tank/apps/postgres  none   100G         none            80G   20G

Significado: Reservaste 100G; el dataset tiene solo 20G “disponibles” incluso si el pool tiene más.
Decisión: si Postgres se acerca a 100G y no querías presión dura de espacio, ajusta o quita la reserva. Si lo querías, alerta sobre ello—las reservas son política, no trivialidad.

Task 12: Validar compresión y espacio lógico (¿está ayudando o perjudicando?)

cr0x@server:~$ zfs get -o name,compressratio,used,logicalused compression,compressratio tank/apps/artifacts
NAME                 PROPERTY       VALUE  SOURCE
tank/apps/artifacts  compression    lz4    inherited from tank/apps
tank/apps/artifacts  compressratio  1.72x  -

Significado: La compresión está activa y es efectiva. Si compressratio está ~1.00x, estás gastando
CPU con poco beneficio. Decisión: mantén la compresión para la mayoría de cargas mixtas; apágala solo con
evidencia, no por intuición.

Task 13: Detectar colisiones de mountpoint (modo común de fallo por herencia)

cr0x@server:~$ zfs get -r -H -o name,value mountpoint tank | sort -k2 | awk 'prev==$2{print "collision:", prev, "between", prevname, "and", $1} {prev=$2; prevname=$1}'
collision: /srv/apps between tank/apps and tank/apps/legacy

Significado: Dos datasets comparten el mismo mountpoint. ZFS no montará felizmente ambos.
Decisión: arregla mountpoints antes del día de reboot. Las colisiones suelen aparecer como “montajes faltantes aleatorios”
dependiendo del orden de montaje.

Task 14: Confirmar qué propiedades son heredables (sabe qué juego estás jugando)

cr0x@server:~$ zfs get -H -o property,values,source all tank/apps | head -n 8
type            filesystem   -
creation        Wed Nov 13 10:12 2024  -
used            120G         -
available       2.1T         -
referenced      256K         -
compressratio   1.23x        -
mounted         yes          -
quota           none         default

Significado: Algunas propiedades muestran una fuente significativa; otras son dinámicas y no lo hacen.
Decisión: enfoca las auditorías de herencia en propiedades de comportamiento (compression, recordsize, atime,
mountpoint, canmount, snapdir, sync, logbias, primarycache/secondarycache, xattr, acltype, propiedades relacionadas con encryption).

Guion de diagnóstico rápido

Este es el flujo de triaje “está lento / no se monta / se quedó sin espacio” que te lleva al culpable de herencia
rápidamente. Ejecútalo como una lista de verificación. La velocidad importa; la corrección importa más.

Primero: define el alcance (¿un dataset o un subárbol?)

  • Identifica el dataset(s) que respaldan la ruta: comprueba el mapeo de mountpoints y zfs list.
  • Determina si los hermanos también están afectados (la herencia a menudo golpea a los hermanos).
  • Pregunta “¿qué padre unificaría estos datasets?” Ahí es donde probablemente ocurrió el cambio.

Segundo: revisa las fuentes de propiedades para los sospechosos principales

  • Problemas de montaje: mountpoint, canmount, readonly, overlay (si aplica), y colisiones.
  • Regresión de rendimiento: recordsize, compression, atime, sync, logbias, primarycache, secondarycache.
  • Sorpresas de espacio: quota, refquota, reservation, refreservation.

Tercero: confirma que el cambio es política, no hardware

  • Salud del pool: zpool status debe estar limpio; si no, estás depurando el problema equivocado.
  • Diff del árbol de datasets: compara fuentes de propiedades entre datasets “buenos” y “malos”.
  • Busca actividad administrativa reciente: historial de shell, notas de gestión de cambios, commits de automatización.

Cuarto: arregla con el menor radio de impacto

  • Si un único hijo necesita un comportamiento diferente, establece una anulación local en el hijo.
  • Si la política es incorrecta para muchos hijos, cambia el padre—pero solo después de auditar el subárbol.
  • Si no puedes decidir con seguridad, restaura valores previos (revertir el cambio de propiedad) y revisa con datos.

Tres mini-historias corporativas desde las minas de herencia

Mini-historia 1: El incidente causado por una suposición equivocada

Una empresa SaaS de tamaño medio tenía un layout de ZFS ordenado: tank/apps para estado de aplicaciones, con hijos
para Postgres, Redis y un blob store. Un nuevo SRE recibió la tarea de “hacer que los backups sean más fáciles de encontrar” en un host
usado por varios equipos. Notó que el dataset padre estaba montado en /srv/apps y quiso que todo estuviera bajo /srv para “consistencia”.

Ejecutó un único comando: puso mountpoint=/srv en tank/apps. El sistema no explotó
de inmediato porque la mayoría de los montajes ya estaban activos. El cambio quedó allí como una piel de plátano.
Esa noche, un reboot rutinario por parcheo del kernel provocó remounts. Algunos datasets fallaron al montarse por
targets “busy”, otros colisionaron con directorios existentes, y unidades de systemd corrieron asumiendo que las rutas existían.

El ticket del incidente parecía una antología de horror: “Postgres no arranca”, “Redis datos faltantes”, “la aplicación
no encuentra uploads”. El pool estaba sano. El IO de disco se veía bien. El problema era que las rutas no estaban donde los servicios
las esperaban.

La solución fue sencilla pero humillante: revertir el mountpoint del padre, establecer mountpoints locales solo donde hacía falta,
y añadir una comprobación pre-reboot que compare los mountpoints actuales contra un inventario aprobado. La lección real
no fue “no toques mountpoint”. Fue “no asumas que las propiedades de dataset se comportan como configuraciones por directorio.”

La línea más afilada del postmortem: nadie revisó el radio de impacto. El comando era correcto. La suposición no lo fue.

Mini-historia 2: La optimización que salió mal

Otra organización almacenaba artefactos de build y capas de contenedores en ZFS. Luchaban por capacidad: el pool iba
acercándose a un uso incómodo. Alguien propuso activar la compresión en la raíz:
zfs set compression=lz4 tank. Es una recomendación común. También no es gratis.

El despliegue se hizo vía automatización. En horas, la monitorización mostró CPU en aumento en un subconjunto de nodos.
La latencia en una API concurrida también subió. El equipo supuso que el cambio en la API no estaba relacionado—hasta que correlacionaron los nodos
con el layout de datasets. La API tenía su dataset bajo tank también, pero nadie pensó que “compartiera”
algo con los artefactos de build.

La compresión no era la villana; era la interacción. Esa API manejaba muchos payloads pequeños ya comprimidos.
Los ratios de compresión rondaban 1.00x. Mientras tanto, los ciclos CPU extra competían con hilos de aplicación en horas punta.
En papel era “sobrecosto menor”. En realidad, fue sobrecosto en el lugar equivocado en el momento equivocado.

Se recuperaron poniendo compression=off localmente en el dataset de la API y dejándola encendida en otros lugares.
El fallo no fue que la compresión sea mala. Fue tratar el árbol de datasets como un archivador estático en vez de un dominio de rendimiento compartido.

La mejora a largo plazo fue política: herencia de alto nivel solo para propiedades seguras y ampliamente beneficiosas,
y datasets “excepción” explícitos documentados y probados. La compresión quedó para la mayoría. La API obtuvo sus propias reglas.

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

Una compañía del ámbito financiero usaba ZFS para servicios multi-tenant internos. Su lead de storage insistía en un ritual aburrido:
cada trimestre, ejecutar una auditoría recursiva de propiedades y subir la salida al repo de infraestructura como artifacto.
No como diagrama. La salida cruda de zfs get -r. Todo el mundo se quejaba de que era burocrático.

Entonces un nuevo despliegue de plataforma introdujo un cambio sutil: un dataset padre quedó con atime=on localmente debido a
una jugada equivocada de “restaurar valores por defecto”. Nadie lo notó durante desarrollo porque las cargas de prueba eran pequeñas.
En producción, servicios con muchas lecturas empezaron a generar escrituras extras. La latencia subió. La amplificación de escritura NVMe
aumentó. Nada catastrófico, solo ese burn lento y costoso.

El ingeniero on-call hizo las comprobaciones habituales. Pool sano. Sin errores. Los patrones de IO parecían “ocupados” pero no fallidos.
Entonces sacó la última instantánea trimestral de propiedades y la comparó con el estado actual. La diferencia saltó:
la fuente de atime cambió en un padre. Eso redujo inmediatamente el alcance a un subárbol y a una sola propiedad.

Solución: restaurar atime=off en el padre correcto, confirmar que los hijos lo heredaron, y añadir un guardrail en
la automatización para prevenir reactivaciones accidentales. Downtime: ninguno. Tiempo de debugging: corto. La práctica aburrida se pagó sola
en un incidente.

La moraleja es poco sexy: los inventarios de propiedades no son papeleo; son máquinas del tiempo. Cuando depuras,
una línea base conocida es un arma.

Errores comunes: síntomas → causa raíz → arreglo

1) “El dataset hijo cambió y nadie lo tocó”

Síntomas: cambios de comportamiento (rendimiento, rutas de montaje, visibilidad de snapshots), pero el dataset hijo no muestra acciones administrativas recientes.

Causa raíz: propiedad heredada de un padre que fue modificada.

Arreglo: ejecuta zfs get -o name,property,value,source en el hijo y traza la fuente. O bien revierte el cambio en el padre o establece una anulación local en el hijo.

2) “Después del reboot, faltan algunos datasets”

Síntomas: servicios fallan al arrancar, mountpoints vacíos, datasets ZFS muestran mounted=no.

Causa raíz: cambio heredado en mountpoint, colisiones de mountpoint, o canmount=off establecido en un padre.

Arreglo: revisa zfs get -r canmount,mountpoint,mounted. Elimina colisiones; establece mountpoints locales en hijos que necesiten rutas únicas; asegúrate de que los padres usados como contenedores tengan canmount=off solo cuando sea intencional.

3) “Regresión de rendimiento después de un tuning ‘inofensivo’”

Síntomas: mayor latencia, más IO, aumento de CPU; sin errores de pool.

Causa raíz: recordsize, atime o compression heredados aplicados a cargas que los detestan.

Arreglo: audita las fuentes de propiedades en el subárbol. Aplica anulaciones locales por carga de trabajo. Evita afinar en padres de alto nivel a menos que puedas justificarlo para cada hijo.

4) “No queda espacio, pero el pool tiene espacio”

Síntomas: la aplicación ve ENOSPC; zpool list muestra espacio libre.

Causa raíz: límites de quota/refquota/reservation/refreservation; a veces expectativas heredadas más que propiedades heredadas.

Arreglo: inspecciona zfs get quota,refquota,reservation,refreservation,available. Ajusta límites y alerta sobre “available” a nivel de dataset, no solo espacio libre del pool.

5) “Snapshots de repente visibles (o desaparecieron)”

Síntomas: aplicaciones recorren directorios .zfs, herramientas de backup se comportan raro, equipos de cumplimiento preguntan.

Causa raíz: snapdir heredado cambiado en un padre.

Arreglo: establece snapdir=hidden o snapdir=visible deliberadamente en el límite de política correcto; verifica recursivamente con zfs get -r snapdir.

6) “Activamos cifrado en el padre; ¿por qué los hijos existentes no están cifrados?”

Síntomas: datasets nuevos están cifrados, los antiguos no; la auditoría lo señala.

Causa raíz: las propiedades de cifrado influyen en la creación e herencia de datasets, pero no puedes cifrar datasets existentes in-place de forma retroactiva.

Arreglo: planifica una migración send/receive a un nuevo árbol cifrado; trata la “herencia” como una plantilla para datasets nuevos, no como una máquina del tiempo para datos viejos.

Listas de verificación / plan paso a paso

Plan A: Cambiar con seguridad una propiedad en un dataset padre

  1. Identifica el subárbol. Lista hijos y mountpoints.

    cr0x@server:~$ zfs list -r -o name,mountpoint tank/apps
    NAME                MOUNTPOINT
    tank/apps           /srv/apps
    tank/apps/postgres  /srv/apps/postgres
    tank/apps/redis     /srv/apps/redis

    Decisión: si no reconoces cada dataset, para. Los hijos desconocidos son donde se esconden las sorpresas.

  2. Audita valores y fuentes de propiedades actuales recursivamente.

    cr0x@server:~$ zfs get -r -o name,property,value,source compression,recordsize,atime,sync tank/apps
    NAME                PROPERTY    VALUE     SOURCE
    tank/apps           compression lz4       local
    tank/apps           recordsize  128K      local
    tank/apps           atime       on        default
    tank/apps           sync        standard  default
    tank/apps/postgres  compression lz4       inherited from tank/apps
    tank/apps/postgres  recordsize  16K       local
    tank/apps/postgres  atime       on        inherited from tank/apps
    tank/apps/postgres  sync        standard  inherited from tank/apps

    Decisión: si hijos importantes tienen anulaciones locales, confirma que sigan correctas después del cambio en el padre.

  3. Modela el impacto. Lista qué datasets heredan actualmente esa propiedad del padre.

    cr0x@server:~$ zfs get -r -H -o name,source atime tank/apps | awk '$2 ~ /tank\/apps/ {print $1}'
    tank/apps/postgres
    tank/apps/redis

    Decisión: si la lista impactada contiene “workloads especiales”, considera anulaciones locales en lugar de cambiar el padre.

  4. Haz el cambio en una ventana controlada (o al menos en condiciones controladas).

    cr0x@server:~$ zfs set atime=off tank/apps

    Decisión: si esto está relacionado con mounts, planifica downtime; los cambios de mountpoint son ruidosos operativamente.

  5. Verifica recursivamente y vigila outliers.

    cr0x@server:~$ zfs get -r -o name,property,value,source atime tank/apps
    NAME                PROPERTY  VALUE  SOURCE
    tank/apps           atime     off    local
    tank/apps/postgres  atime     off    inherited from tank/apps
    tank/apps/redis     atime     off    inherited from tank/apps

    Decisión: si algún dataset siguió con atime=on, tiene una anulación local; decide si eso es correcto.

Plan B: Hacer a un hijo inmune a futuros cambios del padre

  1. Identifica qué propiedades deben fijarse localmente. Típico: recordsize, logbias, primarycache, sync para bases de datos (con cuidado), o mountpoint.
  2. Establece anulaciones locales explícitas y documenta el motivo.

    cr0x@server:~$ zfs set recordsize=16K tank/apps/postgres
    cr0x@server:~$ zfs set primarycache=metadata tank/apps/postgres
    cr0x@server:~$ zfs get -o name,property,value,source recordsize,primarycache tank/apps/postgres
    NAME                PROPERTY      VALUE     SOURCE
    tank/apps/postgres  recordsize    16K       local
    tank/apps/postgres  primarycache  metadata  local

    Decisión: las anulaciones locales son un contrato. Si no puedes justificarlas, solo estarás complicando el futuro.

Plan C: Prevenir sorpresas con una línea base de propiedades

  1. Captura una instantánea recursiva de propiedades para datasets clave.

    cr0x@server:~$ zfs get -r -o name,property,value,source all tank/apps > /var/tmp/zfs-props-tank-apps.txt
    cr0x@server:~$ wc -l /var/tmp/zfs-props-tank-apps.txt
    842 /var/tmp/zfs-props-tank-apps.txt

    Decisión: almacénala en un lugar durable (artifacto del repo, store de gestión de configuración). Si vive solo en el host, morirá con el host.

  2. Diff antes/después de los cambios.

    cr0x@server:~$ diff -u /var/tmp/zfs-props-tank-apps-before.txt /var/tmp/zfs-props-tank-apps-after.txt | head
    --- /var/tmp/zfs-props-tank-apps-before.txt
    +++ /var/tmp/zfs-props-tank-apps-after.txt
    @@
    -tank/apps  atime  on   default
    +tank/apps  atime  off  local

    Decisión: si el diff es mayor de lo esperado, para y reevalúa. Los diffs grandes son donde vienen los outages.

Preguntas frecuentes

1) ¿Cómo sé si una propiedad de dataset es heredada?

Usa zfs get y mira la columna source. Si dice “inherited from …”, esa es tu respuesta.

2) Si establezco una propiedad en un padre, ¿siempre afecta a todos los hijos?

Afecta a los hijos que no tienen una anulación local para esa propiedad, y solo a las propiedades que son heredables.
Los hijos con valores locales quedan aislados.

3) ¿Cuál es la diferencia entre zfs inherit y establecer una propiedad a su valor por defecto?

zfs inherit elimina la configuración local, causando que el dataset herede de su padre (o vuelva al default si ningún padre lo define).
Establecer un valor explícitamente lo hace local, incluso si coincide con el default.

4) ¿Por qué cambiar mountpoint rompió cosas aunque es “solo una ruta”?

Porque las rutas son contratos con servicios, configs y el orden de arranque. La herencia puede desplazar el layout de mounts de todo un subárbol.
No es solo renombrar; es un cambio de topología.

5) ¿Es seguro activar compression=lz4 en el dataset superior del pool?

A menudo sí, pero “seguro” depende de la carga y la holgura de CPU. Audita ratios de compresión y uso de CPU.
Si un dataset almacena mayormente datos ya comprimidos, configura una excepción local.

6) ¿Debo afinar recordsize en un padre de alto nivel?

Usualmente no. Usa recordsize a nivel padre solo cuando los hijos son homogéneos.
Para cargas mixtas, fija recordsize localmente por dataset que represente una carga de trabajo.

7) ¿Puedo cifrar retroactivamente datasets existentes poniendo propiedades de cifrado en el padre?

No. Normalmente necesitas una migración send/receive a un nuevo dataset cifrado.
Las propiedades de cifrado del padre guían la creación/herencia de datasets nuevos, no la conversión in-place de datos viejos.

8) ¿Cómo encuentro qué dataset cubre un directorio cuando los montajes confunden?

Revisa la tabla de mounts de ZFS con zfs mount y compara mountpoints, o usa las herramientas de montaje del SO.
Luego consulta propiedades en ese dataset específicamente.

9) ¿Por qué dos datasets muestran el mismo valor de propiedad pero se comportan diferente después?

Mismo valor, distinta fuente. Un valor de default no cambiará a menos que lo cambies.
Un valor heredado de un padre puede cambiar cada vez que el padre cambia.

10) ¿Cómo mantengo la herencia pero evito sorpresas?

Coloca límites de política donde están los límites organizacionales: un padre por clase de workload.
Luego ejecuta auditorías recursivas y guarda una línea base para poder demostrar qué cambió.

Conclusión: próximos pasos que puedes hacer

La herencia de propiedades de ZFS no es un detalle académico. Es un plano de control de producción. Puede evitarte la proliferación de configs,
y puede dejarte fuera de combate cuando un “cambio pequeño” resulta ser un desplazamiento de política en todo un subárbol.

Haz esto a continuación:

  1. Elige tres árboles de datasets críticos (bases de datos, backups y lo que llene discos) y ejecuta una auditoría recursiva de propiedades. Guárdala.
  2. Marca los datasets de workload explícitamente y establece anulaciones locales donde la carga realmente difiera (especialmente recordsize, atime, propiedades de montaje).
  3. Adopta el hábito del radio de impacto: antes de cambiar un padre, lista todos los hijos que heredan esa propiedad y toma una decisión consciente.
  4. Operationaliza la detección de deriva: haz diff de tu línea base guardada contra las propiedades actuales después de cada ventana de cambios.

Si solo recuerdas una cosa: nunca mires el valor de una propiedad ZFS sin mirar su fuente.
Ahí es donde se esconde la sorpresa.

← Anterior
ashift de ZFS: la desalineación silenciosa que reduce el rendimiento a la mitad
Siguiente →
MySQL vs PostgreSQL: respaldos y restauraciones — ¿quién te devuelve en línea más rápido?

Deja un comentario