Semántica de sincronización ZFS NFS: Por qué los clientes cambian la seguridad de tus escrituras

¿Te fue útil?

Montaste un servidor ZFS. Configuraste sync=standard como un adulto responsable. Incluso compraste SSD “enterprise” para el SLOG.
Entonces un equipo de bases de datos monta el export, ejecuta una carga y de repente tu gráfica de latencia de escrituras parece un sismógrafo. O peor: recibes
un ticket tras un fallo que empieza con “perdimos transacciones comprometidas”.

El remate es cruel: en NFS, el cliente puede cambiar lo que “escritura segura” significa en el servidor. No por malicia, sino por semántica del protocolo, opciones
de montaje y comportamiento de la aplicación. ZFS es determinista; los clientes NFS son… creativos.

La idea central: ZFS solo puede cumplir lo que el cliente realmente pide

ZFS te da un contrato claro: si una operación es síncrona, ZFS no la reconocerá hasta que sea segura según sus reglas—es decir, hasta que esté
en almacenamiento estable como describe el registro de intención de ZFS (ZIL) y el orden de escritura del pool. Si es asíncrona, ZFS puede hacer buffering.

NFS complica esto porque el servidor no decide cuándo una aplicación considera una escritura “confirmada”. El cliente decide cuándo emitir escrituras
estables, cuándo enviar un COMMIT y si “miente” (accidentalmente) mediante políticas de caché u opciones de montaje. Muchas aplicaciones no
llaman a fsync() tan a menudo como imaginas; muchas librerías “agrupan durabilidad” hasta puntos de control; y muchos clientes NFS intentan
trucos de rendimiento que son perfectamente legales en el protocolo pero sorprenden a los ingenieros de almacenamiento.

Aquí está la verdad operativa incómoda: puedes ejecutar la misma configuración de servidor ZFS y obtener resultados de durabilidad materialmente distintos
según el sistema operativo cliente, la versión de NFS, las opciones de montaje y el comportamiento de flush de la aplicación. Dos clientes pueden montar
el mismo export y tener perfiles de pérdida por crash diferentes.

Si gestionas almacenamiento compartido, debes tratar a los clientes NFS como parte del camino de escritura. No son “solo consumidores”. Son participantes
en tu protocolo de durabilidad—a menudo sin saber que se apuntaron.

Datos e historia interesantes que siguen importando

  • NFS empezó como “sin estado” por diseño (era v2/v3). El servidor no mantenía estado por cliente, lo que facilitaba recuperación y escalado, pero empujó la complejidad de durabilidad a los clientes.
  • NFSv3 introdujo el procedimiento COMMIT. Existe porque las respuestas WRITE pueden representar almacenamiento “inestable”; COMMIT pide al servidor que lleve esos bytes a almacenamiento estable.
  • NFSv4 se movió hacia operación con estado. Añadió locks, delegaciones y un modelo más integrado—aun así, “¿cuándo está en almacenamiento estable?” sigue siendo una realidad negociada.
  • ZFS desacopla intencionalmente “reconocer” de “commit del TXG en disco”. Las escrituras síncronas pueden satisfacerse con el ZIL sin esperar al siguiente sync del grupo de transacciones (TXG).
  • El ZIL no es una caché de escritura para todo. Solo registra lo necesario para rehacer operaciones síncronas tras un crash; se trata de corrección, no de aceleración por defecto.
  • Un dispositivo SLOG separado es solo “ZIL en un medio estable más rápido”. No almacena tus datos a largo plazo; guarda registros de intención hasta que el TXG confirme.
  • sync=disabled existe porque la gente lo seguía pidiendo. También existe porque a veces quieres velocidad más que verdad. Tu futuro informe de incidente decidirá si fue prudente.
  • Linux y varios UNIX difieren en cuán agresivamente emiten COMMIT. Algunos bufferizan y agrupan con gusto; otros fuerzan estabilidad con más frecuencia según opciones de montaje y patrones de carga.
  • El orden de escrituras y las barreras importan incluso con un buen SLOG. Si el dispositivo miente sobre flushes, obtienes acknowledgements por escrituras que nunca fueron realmente estables.

Semántica de escritura NFS en la práctica: inestable, estable y “te juro que está bien”

La promesa de NFS no es “cada escritura es durable”

NFS no es un protocolo de dispositivo de bloques. Es un protocolo de archivos con un contrato que depende de qué operaciones el cliente solicita y qué semántica
espera.

Con NFSv3, un cliente puede enviar operaciones WRITE que el servidor puede bufferizar. La respuesta WRITE puede indicar si los datos se consideran estables
(FILE_SYNC) o no (UNSTABLE). Si son inestables, se espera que el cliente envíe un COMMIT para hacerlos estables antes de tratarlos
como durables.

Con NFSv4, el modelo evoluciona pero la misma pregunta fundamental permanece: ¿qué promete el servidor en el momento de responder? El cliente puede usar
operaciones compuestas y reglas de caché distintas, pero sigue decidiendo cuándo exigir estabilidad.

Escritura estable es una negociación, no una vibra

“Estable” en NFS no es un concepto filosófico. Es concreto: estable significa que el servidor ha puesto la escritura en almacenamiento no volátil de modo que
sobreviva un crash según las reglas del servidor. Eso puede ser RAM con batería, NVRAM, un disco con flush de caché correcto o—en ZFS—típicamente la ruta ZIL/SLOG
para semánticas síncronas.

Pero el cliente no siempre pide estable. Puede emitir escrituras inestables y enviar COMMIT más tarde. O puede confiar en semánticas close-to-open y caché de atributos,
no en semánticas de durabilidad. O la app puede “confirmar” a nivel de base de datos dejando que el SO bufferice.

Dos palabras exactas que disparan mundos muy diferentes: fsync y O_DSYNC

Las aplicaciones expresan necesidades de durabilidad usando llamadas al sistema como fsync(), fdatasync() y banderas como O_SYNC /
O_DSYNC. En un sistema de archivos local, estas suelen mapear bastante directo a “no me mientas”.

Sobre NFS, esas llamadas se convierten en operaciones a nivel de protocolo que pueden incluir COMMIT o escrituras estables—dependiendo de la configuración del cliente.
Si el cliente elige reagrupar o retrasar COMMIT, tu servidor puede ser “correcto” pero las expectativas de la app pueden violarse tras un fallo.

Broma #1: la durabilidad en NFS es como la política de oficina—todos están de acuerdo en la reunión, y luego las decisiones reales pasan en privado.

Lado ZFS: sync, ZIL, SLOG, txg, y dónde vive la verdad

Grupos de transacciones: el gran ritmo debajo de todo

ZFS agrupa cambios en memoria y periódicamente los compromete a disco en grupos de transacciones (TXGs). Esta es una estrategia de rendimiento central: convierte
escrituras pequeñas y aleatorias en patrones de E/S más secuenciales y permite a ZFS optimizar la asignación.

La cadencia del TXG suele ser del orden de unos segundos. Eso está bien para escrituras asíncronas: la app recibe un ACK rápido y ZFS lo compromete después.
Pero para escrituras síncronas, esperar al siguiente commit de TXG es demasiado lento. Ahí es donde entra el ZIL.

ZIL: registro de intención para operaciones síncronas

El ZIL (ZFS Intent Log) registra suficiente información sobre operaciones síncronas para poder reproducirlas tras un crash. No es un diario completo de todos los cambios.
Es una red de seguridad específicamente para operaciones que se reconocieron como “hechas” antes de que el TXG las hiciera permanentes en disco.

Sin un dispositivo separado, el ZIL vive en el pool principal. Con un dispositivo de log separado (SLOG), ZFS puede colocar esos registros de intención en un almacenamiento
más rápido y de baja latencia, reduciendo el coste de los ACKs síncronos.

Operativamente: el ZIL es donde pagas por la honestidad. Si exiges semánticas síncronas, estás pidiendo a ZFS hacer trabajo extra ahora en lugar de después.

La propiedad de dataset sync: la palanca que la gente usa mal

ZFS expone sync como propiedad de dataset:
standard, always y disabled.

  • sync=standard: honra las solicitudes sync. Si el cliente/app pide sync, hazlo; de lo contrario bufferiza.
  • sync=always: trata todas las escrituras como sync, incluso si el cliente no lo pidió. Es el modo de “no confío en ti”.
  • sync=disabled: miente. Reconoce solicitudes sync sin realmente asegurar almacenamiento estable.

En tierra NFS, sync=standard no es lo mismo que “seguro”. Es “seguro si el cliente pide seguridad”.
Y los clientes tienen múltiples formas de no pedirla.

Qué significa “seguro” depende de toda la cadena

Un ACK síncrono solo es tan honesto como el eslabón más débil que puede fingir que los datos son estables cuando no lo son:

  • Configuración de caché de unidad y comportamiento de flush
  • Caché del controlador y protección por batería/flash
  • Protección ante pérdida de energía (PLP) y orden de escrituras del dispositivo SLOG
  • Pila de almacenamiento del hipervisor si estás virtualizado
  • Caché del lado cliente y opciones de montaje NFS

Puedes hacer todo bien en ZFS y aun así sufrir por un cliente configurado para tratar close() como “suficiente” sin forzar escrituras estables, o por
un SSD “rápido” que reconoce flushes como si leyeras un cuento para dormir.

Una cita que se mantiene: “La esperanza no es una estrategia.” — idea parafraseada frecuentemente atribuida a ingenieros de ops/confiabilidad.

Por qué el cliente cambia la seguridad: montajes, cachés y patrones de app

Las opciones de montaje NFS pueden intercambiar silenciosamente durabilidad por rendimiento

En muchos clientes, la diferencia entre “sync” y “async” no es un simple interruptor; es una combinación de comportamientos:

  • Caching en tiempo de montaje. El cacheo de atributos, cacheo de entradas de directorio y la caché de páginas del cliente pueden cambiar cuándo se empujan las escrituras.
  • Agrupación de escrituras. Los clientes pueden agrupar escrituras pequeñas en RPCs más grandes, reduciendo overhead pero retrasando la estabilidad.
  • Comportamiento de commit. El cliente puede enviar COMMITs de forma perezosa, o solo en fsync/close según la política.
  • Montajes hard vs soft. No controla directamente la durabilidad, pero cambia cómo se manifiestan fallos (bloqueo vs error), lo que altera el comportamiento de la aplicación bajo estrés.

No puedes asumir los valores por defecto del cliente. Varían por versión de SO, distro, kernel e incluso baselines de seguridad.

Las aplicaciones no son consistentes respecto a los flushes

Las bases de datos son los culpables obvios, pero muchos sistemas “aburridos” hacen cosas peligrosas:

  • Colas de mensajes que agrupan fsync cada N mensajes.
  • Registradores que llaman a fsync solo al rotar archivos.
  • Sistemas de build que “no les importa” hasta que de repente sí les importa (¿repositorios de artefactos?).
  • ETL que asumen que rename es atómico y durable en todas partes.

En sistemas locales, estos patrones pueden ser tolerables. Sobre NFS, pueden convertirse en ventanas largas de datos reconocidos pero no estables,
especialmente si el cliente usa escrituras inestables y retrasa COMMIT.

Exports del servidor: lo que permites importa

Las opciones de export del servidor no suelen decir “miente sobre durabilidad”, pero sí influyen en el comportamiento del cliente: sabores de seguridad, subtree checking,
estabilidad de FSID y comportamientos de delegación (en NFSv4) pueden cambiar el cacheo y patrones de reintento.

En servidores Linux (incluyendo muchas implementaciones ZFS-on-Linux), exportfs y los hilos nfsd pueden convertirse en cuellos de botella que
parecen “latencia de disco”. Si no mides ambos, culparás la capa equivocada y la “arreglarás” comprando SSDs.

Realidades del SLOG: cuándo ayuda, cuándo perjudica y cuándo es teatro

Cuándo un SLOG ayuda

Un SLOG ayuda cuando tienes muchas escrituras síncronas y la latencia del pool es superior a la de un dispositivo dedicado de baja latencia. Esto
es común en:

  • Cargas NFS donde los clientes emiten fsync/ escrituras estables con frecuencia (bases de datos, imágenes VM en NFS, algunos sistemas de correo)
  • Escrituras pequeñas aleatorias y síncronas cuando los vdevs del pool son HDD o están ocupados
  • Aplicaciones sensibles a latencia donde cada escritura sync bloquea un hilo

El SLOG trata sobre latencia de ACK, no sobre rendimiento global. Si no dependes mucho de sync, no moverá la aguja.

Cuándo un SLOG no hace nada

Si la mayoría de las escrituras son asíncronas, la ruta ZIL/SLOG no es tu cuello de botella. Tu limitación será el rendimiento de commit del TXG, límites de datos sucios,
o amplificación de lectura.

Además: si tus clientes hacen escrituras inestables y retrasan COMMIT, un SLOG rápido solo ayudará en el momento en que se haga el COMMIT. Hasta entonces, no estás
pagando el coste sync—lo que significa que podrías estar acumulando riesgo, no solucionando latencia.

Cuándo un SLOG perjudica

Un dispositivo SLOG malo puede arruinar la latencia. ZFS envía registros de intención en un patrón que quiere escrituras de baja latencia consistentes con
semántica de flush correcta. Las SSDs de consumo a menudo:

  • tienen latencia impredecible bajo escrituras síncronas sostenidas,
  • carecen de protección ante pérdida de energía,
  • reconocen flushes de forma optimista,
  • colapsan cuando se agota la caché SLC.

Así es como obtienes la gráfica clásica: la latencia p99 está bien… hasta que de repente no lo está, y entonces se queda mal.

Broma #2: una SSD “rápida” sin protección ante pérdida de energía es como un currículum que pone “trabaja en equipo”—técnicamente posible, pero deberías verificarlo.

sync=always: la opción nuclear que a veces es correcta

Si no puedes confiar en que los clientes pidan estabilidad correctamente, sync=always es la herramienta contundente que obliga a ZFS a tratar cada escritura
como síncrona. Reduce el espacio para la creatividad del lado cliente.

También aumenta la latencia y expondrá cada eslabón débil: SLOG, pool, controlador y red. Úsalo de forma quirúrgica: por dataset, por export, para cargas que realmente lo necesiten.

Guion de diagnóstico rápido

La forma más rápida de depurar dolor de sync en NFS sobre ZFS es evitar debatir filosofía y, en su lugar, responder tres preguntas en orden:
(1) ¿estamos haciendo escrituras síncronas?, (2) ¿dónde está la latencia?, y (3) ¿el sistema miente sobre la durabilidad de los flushes?

Primero: confirma si la carga es realmente síncrona

  • Revisa la propiedad sync del dataset ZFS y confirma expectativas.
  • Revisa las opciones de montaje del cliente NFS y si las apps llaman a fsync.
  • Observa la actividad ZIL/SLOG y las tasas de COMMIT NFS.

Segundo: localiza el dominio del cuello de botella (CPU, red, SLOG, pool)

  • Si hilos del servidor NFS están saturados, no es “latencia de disco”, es un cuello de botella del servidor.
  • Si la latencia del SLOG se dispara, las escrituras síncronas se dispararán aunque el pool esté bien.
  • Si los vdev del pool están ocupados, los commits TXG se retrasarán y las cargas asíncronas se estancarán.

Tercero: valida el comportamiento de almacenamiento estable de extremo a extremo

  • Confirma que el dispositivo SLOG tiene PLP y no está virtualizado detrás de cachés writeback.
  • Confirma la política de caché de escritura del disco y que los flushes se honran.
  • Confirma que no estás ejecutando sync=disabled en algún sitio “temporalmente”.

Tareas prácticas: comandos, salidas y decisiones

Estas son las comprobaciones que realmente ejecuto cuando alguien dice “NFS está lento” o “perdimos escrituras”. Cada una incluye: comando, salida de ejemplo, qué significa,
y la decisión que tomas.

Tarea 1: Identificar el dataset y su política sync

cr0x@server:~$ zfs get -o name,property,value,source sync tank/nfs/db
NAME         PROPERTY  VALUE     SOURCE
tank/nfs/db  sync      standard  local

Qué significa: ZFS honrará las solicitudes sync, pero no las forzará.

Decisión: Si la carga necesita durabilidad estricta independientemente del comportamiento del cliente, considera sync=always (en ese dataset), no en todo el pool.

Tarea 2: Busca la clásica trampa: sync disabled

cr0x@server:~$ zfs get -r -o name,property,value,source sync tank/nfs
NAME        PROPERTY  VALUE     SOURCE
tank/nfs    sync      standard  default
tank/nfs/db sync      standard  local
tank/nfs/ci sync      disabled  local

Qué significa: Un subárbol está mintiendo sobre las escrituras sync.

Decisión: Trata esto como un riesgo de producción. Si es intencional, documenta el alcance y las expectativas de pérdida por crash; de lo contrario, arréglalo ahora.

Tarea 3: Confirma si hay (o no) un SLOG

cr0x@server:~$ zpool status -v tank
  pool: tank
 state: ONLINE
config:

        NAME                         STATE     READ WRITE CKSUM
        tank                         ONLINE       0     0     0
          mirror-0                   ONLINE       0     0     0
            ata-HDD_A                ONLINE       0     0     0
            ata-HDD_B                ONLINE       0     0     0
        logs
          nvme-SLOG0                 ONLINE       0     0     0

errors: No known data errors

Qué significa: Las escrituras síncronas pueden ser reconocidas vía el dispositivo de log.

Decisión: Si tienes NFS con muchas escrituras sync y no hay SLOG, espera mayor latencia. Si tienes SLOG, valida que realmente sea bueno.

Tarea 4: Ver la latencia del dispositivo SLOG bajo carga (indicador rápido)

cr0x@server:~$ iostat -x 1 3 /dev/nvme0n1
Linux 6.5.0 (server)   12/26/2025

avg-cpu:  %user %nice %system %iowait  %steal %idle
           3.2   0.0    5.4    1.1     0.0   90.3

Device            r/s     w/s   rkB/s   wkB/s  avgrq-sz avgqu-sz await r_await w_await  svctm  %util
nvme0n1           0.0  8200.0     0.0  65600.0     16.0     9.1   1.2    0.0    1.2    0.1   82.0

Qué significa: ~1.2 ms de await de escritura a alta tasa es aceptable; si ves decenas de ms, tu SLOG es un problema.

Decisión: Si w_await se dispara durante “tormentas de sync”, reemplaza el SLOG por un dispositivo con PLP o quítalo si empeora las cosas.

Tarea 5: Verifica recordsize del dataset y ajuste a la carga (latencia vs rendimiento)

cr0x@server:~$ zfs get -o name,property,value recordsize,logbias,primarycache tank/nfs/db
NAME         PROPERTY      VALUE     SOURCE
tank/nfs/db  recordsize    128K      local
tank/nfs/db  logbias       latency   local
tank/nfs/db  primarycache  all       default

Qué significa: logbias=latency empuja a ZFS a favorecer el log para escrituras sync; recordsize afecta la amplificación lectura/escritura para bases de datos.

Decisión: Para I/O aleatorio tipo DB, considera un recordsize más pequeño (por ejemplo, 16K) después de medir. Mantén logbias=latency para datasets con muchas sync.

Tarea 6: Comprueba presión TXG y stalls por “dirty data”

cr0x@server:~$ cat /proc/spl/kstat/zfs/arcstats | egrep 'dirty_data|txg_sync|txg_quiesce'
txg_sync_delay                     5
txg_quiesce_delay                  1
dirty_data_max                  8589934592
dirty_data                      2147483648

Qué significa: Si dirty_data se acerca a dirty_data_max, ZFS limitará a los escritores—los clientes verán stalls no relacionados con la semántica NFS.

Decisión: Si estás siendo regulado, revisa el throughput de commit del pool, saturación de vdev y si un “SLOG rápido” está enmascarando un pool lento.

Tarea 7: Vigila la saturación de hilos del servidor NFS (nfsd en Linux)

cr0x@server:~$ ps -eLo pid,comm,psr,pcpu,stat | awk '$2=="nfsd" {sum+=$4} END {print "total_nfsd_cpu="sum"%"}'
total_nfsd_cpu=380%

Qué significa: Los hilos nfsd están consumiendo CPU. Desde el cliente puede parecer “el almacenamiento es lento”.

Decisión: Si la CPU es alta, ajusta el conteo de hilos nfsd, revisa overhead de cifrado/autenticación y perfila la mezcla de RPC antes de tocar ZFS.

Tarea 8: Confirma versiones de protocolo y opciones de montaje en un cliente

cr0x@server:~$ nfsstat -m
/mnt/db from server:/tank/nfs/db
 Flags: rw,relatime,vers=3,rsize=1048576,wsize=1048576,hard,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=10.0.0.10,mountvers=3,mountproto=tcp,local_lock=none

Qué significa: NFSv3 con rsize/wsize grandes; montaje hard. No se muestra una opción sync/async explícita aquí, pero la versión importa para el comportamiento COMMIT.

Decisión: Si las expectativas de durabilidad son estrictas, evalúa el comportamiento de NFSv4.1+ y opciones cliente; alinea un perfil de montaje soportado por clase de carga.

Tarea 9: Observa la tasa de COMMIT en el servidor (¿el cliente fuerza estabilidad?)

cr0x@server:~$ nfsstat -s | egrep 'commit|write'
write        182938
commit        12480

Qué significa: Muchas escrituras pero relativamente pocos commits sugiere escrituras inestables siendo agrupadas, o cargas que no llaman a fsync frecuentemente.

Decisión: Si la carga afirma “cada transacción es durable”, este desacople es tu pista. Audita la política fsync de la aplicación y ajustes de caché del cliente.

Tarea 10: Confirma las opciones de export que se están sirviendo

cr0x@server:~$ exportfs -v | sed -n '/tank\/nfs\/db/,+2p'
/tank/nfs/db   10.0.0.0/24(rw,wdelay,root_squash,sec=sys,no_subtree_check,fsid=120)

Qué significa: Export estándar; wdelay puede cambiar el comportamiento de coalescencia de escrituras en el lado servidor.

Decisión: No cambies perillas de export al azar esperando durabilidad. Usa exports para controlar acceso y corrección; usa ZFS y política cliente para semánticas sync.

Tarea 11: Valida que el dispositivo SLOG no esté fallando o siendo evitado

cr0x@server:~$ zpool iostat -v tank 1 2
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                        2.10T  5.10T      0   2200      0  45.0M
  mirror-0                  2.10T  5.10T      0   2100      0  44.0M
    ata-HDD_A                   -      -      0   1050      0  22.0M
    ata-HDD_B                   -      -      0   1050      0  22.0M
logs                            -      -      0    800      0  12.5M
  nvme-SLOG0                    -      -      0    800      0  12.5M
--------------------------  -----  -----  -----  -----  -----  -----

Qué significa: El dispositivo de log está viendo actividad de escritura. Si siempre es cero durante una carga “sync”, o la carga no es sync o tu configuración no es la que crees.

Decisión: Correlaciona con comportamiento de la app y COMMIT NFS. No compres hardware hasta probar que se están produciendo escrituras sync.

Tarea 12: Comprueba si la latencia percibida por el cliente es red o E/S del servidor

cr0x@server:~$ ss -tin sport = :2049 | head -n 12
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
ESTAB 0      0      10.0.0.10:2049     10.0.0.51:49822
	 cubic wscale:7,7 rto:204 rtt:0.289/0.032 ato:40 mss:1448 pmtu:1500 rcvmss:1448 advmss:1448 cwnd:10 bytes_sent:23849342 bytes_acked:23849210 bytes_received:14920345 segs_out:22110 segs_in:21540 data_segs_out:18900 data_segs_in:17620 send 400.8Mbps lastsnd:8 lastrcv:8 lastack:8 pacing_rate 801.6Mbps unacked:1

Qué significa: RTT es submilisegundo; la red no es la fuente primaria de latencia en este instante.

Decisión: Concéntrate en CPU del servidor y latencia de almacenamiento. Si RTT son varios ms o los retransmits suben, arregla la red primero.

Tarea 13: Verifica que ZFS no sufra errores de checksum o de dispositivo (asesinos silenciosos de latencia)

cr0x@server:~$ zpool status -x
all pools are healthy

Qué significa: No hay fallos conocidos. Si esto no está limpio, deja de “tunear rendimiento” y empieza respuesta a incidentes.

Decisión: Reemplaza dispositivos fallando, haz scrub y reevalúa. Un pool degradado puede parecer exactamente como “NFS sync es lento”.

Tarea 14: Confirma que sync=always está aplicado donde crees

cr0x@server:~$ zfs get -o name,property,value,source sync tank/nfs/db
NAME         PROPERTY  VALUE   SOURCE
tank/nfs/db  sync      always  local

Qué significa: ZFS tratará cada escritura como sync para este dataset, independientemente de las solicitudes del cliente.

Decisión: Espera mayor latencia; asegura que el SLOG sea sólido y monitoriza p95/p99. Usa esto para datos que deben sobrevivir crashes sin ambigüedad.

Tres microhistorias corporativas desde el campo

Microhistoria 1: El incidente causado por una suposición errónea

Una empresa mediana operaba un clúster NFS respaldado por ZFS para servicios internos. Un equipo alojaba la capa de persistencia de una cola de trabajos en un export NFS.
El equipo de almacenamiento había hecho los deberes: pool espejado, SLOG con un “NVMe rápido”, sync=standard y scrubs rutinarios.

El equipo de la cola migró de una distro Linux a otra durante una renovación de plataforma. Misma versión de la app, mismos datos, mismo target de montaje.
Tras un corte de energía (ni siquiera dramático; el tipo que solo luego se vuelve dramático), un trozo de trabajos reconocidos reapareció como “nunca procesados”.
Peor, un conjunto más pequeño desapareció.

La primera ronda de depuración fue previsiblemente inútil. La gente discutía “ZFS es copy-on-write así que no puede perder datos” y “NFS es fiable sobre TCP”
como si esas frases cerraran incidentes. El equipo de almacenamiento ejecutó zpool status—saludable. El equipo de red mostró pocos retransmits.
El equipo de la cola insistía que su app “fsyncea en commit”.

El avance vino de mirar el comportamiento del protocolo, no las creencias. En el cliente, nfsstat -m mostró una versión de NFS diferente y
defaults de caché distintos. En el servidor, las llamadas COMMIT eran mucho menos frecuentes de lo esperado bajo carga. La aplicación sí llamaba fsync—pero
el patrón de I/O estaba bufferizado y la política del cliente retrasó la frontera de estabilidad más de lo que el equipo creía.

La solución fue aburrida: estandarizar el perfil de montaje del cliente para esa carga, y poner el dataset en sync=always hasta que todos probaran
el contrato de durabilidad de la aplicación. Costó latencia. Compró claridad. Es un trade-off que puedes explicar en un postmortem sin sudar.

Microhistoria 2: La optimización que salió mal

Otra organización servía directorios home y artefactos de build por NFS. Querían pipelines de CI más rápidos. Alguien encontró el botón mágico:
sync=disabled en el dataset que sustentaba la caché de builds. Se justificó como “suficientemente seguro porque los artefactos se pueden reconstruir”.

Durante un tiempo, funcionó. Las pipelines de CI se aceleraron. La latencia de almacenamiento bajó. Todos felicitaron el ticket de cambio por “mejorar la utilización”.
Y luego una dependencia no obvia les mordió: una pipeline de release también escribía metadatos firmados en ese mismo árbol. El metadato era “pequeño y rápido”,
así que nadie lo tuvo en cuenta. También resultó ser lo que no se puede reconstruir fácilmente sin rehacer ceremonias de seguridad.

Ocurrió un crash de host durante una release. ZFS había reconocido solicitudes sync sin almacenamiento estable. El archivo de metadatos existía, pero su contenido
era más antiguo. Algunos builds se enviaron con metadatos desajustados. No fue catastrófico, pero el equipo de cumplimiento se involucró y de pronto “artefactos reconstruibles”
dejó de ser una afirmación cómoda.

El fallo no fue que ZFS sea voluble. Fue la organización siendo imprecisa sobre clases de datos en exports compartidos. Optimizaron una carga y arrastraron por error
otra con requisitos más estrictos a la misma política de durabilidad.

La solución fue estructural: separar datasets por requisitos de durabilidad, aplicar sync=standard o sync=always según corresponda, y
mantener “trucos inseguros de velocidad” detrás de puntos de montaje y controles de acceso explícitos. También: borrar la palabra “temporal” de los tickets de cambio;
es la palabra más permanente en ops.

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

Una firma de servicios financieros tenía una plataforma ZFS NFS que alojaba una mezcla de servicios, incluyendo una app stateful cuyo proveedor era muy explícito:
“debe tener escrituras estables en commit”. Los ingenieros de almacenamiento no adivinaron. Crearon un dataset y export separados solo para esa app.
sync=always, un SLOG PLP verificado y un estándar de montaje cliente por escrito. Sin excepciones.

No fue popular. El equipo de la app se quejó de latencia comparado con su SSD local previo. El equipo de almacenamiento no discutió sentimientos.
Mostraron una prueba simple: con sync=standard y el montaje actual de la app, la frecuencia de fsync era menor de lo esperado y el comportamiento de COMMIT
era por ráfagas. Con sync=always, la curva de latencia fue estable y la ventana de pérdida por crash se volvió fácil de razonar.

Meses después, ocurrió un incidente feo en otra parte: un bug de firmware hacía que un subconjunto de servidores se reinicializara bajo patrones I/O específicos.
Muchos sistemas tuvieron rarezas tras el reinicio. Esa app stateful no. Sin corrupción misteriosa, sin “tenemos que reejecutar jobs”, sin retrocesos silenciosos.

La razón no fue heroísmo. Fue que alguien había documentado el requisito de durabilidad, lo había aplicado en el servidor y se negó a mezclarlo con cargas
“rápidas pero descuidadas”. Esa es la clase de aburrimiento que quieres en tu currículum.

Errores comunes: síntomas → causa raíz → solución

1) “Pusimos sync=standard, así que estamos seguros.”

Síntomas: Pérdida de datos tras crash a pesar de “sync habilitado”, o durabilidad inconsistente entre clientes.

Causa raíz: Los clientes no pidieron escrituras estables de forma consistente (escrituras inestables + COMMIT retrasado, política de caché, app no fsyncea).

Solución: Para cargas estrictas, usa sync=always por dataset/export; estandariza opciones de montaje cliente; verifica comportamiento COMMIT/fsync con métricas.

2) “Un SLOG hará todo más rápido.”

Síntomas: Sin mejora, o peor p99 tras añadir SLOG.

Causa raíz: La carga es mayoritariamente async; o el dispositivo SLOG tiene mala latencia/honestidad de flush.

Solución: Mide tasa de COMMIT/escrituras sync primero. Usa SLOG de grado PLP o quita el malo. No hagas cargo-cult con SLOGs.

3) “sync=disabled está bien para datos no críticos.”

Síntomas: Archivos aleatoriamente obsoletos, metadatos truncados, “existe pero es más viejo”, reconciliación post-crash dolorosa.

Causa raíz: Datos no críticos compartieron dataset/export con datos críticos, o “no crítico” resultó crítico durante un incidente.

Solución: Separa datasets por clase de durabilidad. Asegura exports inseguros. Haz el riesgo explícito y auditable.

4) “NFS es lento; los discos deben ser lentos.”

Síntomas: Alta latencia en cliente pero el pool parece bien; picos de CPU en servidor.

Causa raíz: Saturación de hilos nfsd, overhead de auth, mezcla de RPC con mucha metadata, o efectos de serialización por cliente único.

Solución: Mide CPU servidor, mezcla de ops en nfsstat, conteos de hilos y RTT de red. Escala hilos nfsd y ajusta exports antes de comprar discos.

5) “Perdimos escrituras, así que ZFS está corrupto.”

Síntomas: Pérdida a nivel de aplicación sin errores de pool; sin errores de checksum.

Causa raíz: Ventana de pérdida por crash debido a comportamiento async o flushes mentirosos (sync disabled, SLOG/disco malo).

Solución: Audita propiedades sync y semántica de flush en hardware; aplica semánticas sync en servidor para datos críticos.

6) “Cambiamos a NFSv4 así que está arreglado.”

Síntomas: Confusión de durabilidad igual, mensajes de error distintos.

Causa raíz: La versión por sí sola no fuerza a las apps a fsyncear ni a los clientes a pedir almacenamiento estable en el momento correcto.

Solución: Mantén el foco en: comportamiento de flush de la app, política de caché del cliente, propiedad sync del dataset y almacenamiento estable honesto.

Listas de verificación / plan paso a paso

Plan A: Quieres durabilidad estricta para una carga NFS

  1. Crea un dataset dedicado. No lo compartas con cargas “rápidas y descuidadas”.
  2. Configura semánticas sync en el servidor. Usa sync=always si no puedes controlar totalmente el comportamiento de flush del cliente.
  3. Usa un SLOG verificado si la latencia sync importa. PLP requerido; SLOG espejado si no toleras riesgo de fallo del dispositivo de log para disponibilidad.
  4. Estandariza montajes cliente. Documenta la versión NFS soportada y opciones de montaje. Trata desviaciones como no soportadas.
  5. Prueba el comportamiento ante crash. No “benchmarks”, sino simulaciones reales de reboot/pérdida de energía en staging que se parezcan a producción.
  6. Monitorea señales del protocolo. Sigue tasas write/commit, latencia p95 y saturación CPU del servidor.

Plan B: Quieres rendimiento y puedes aceptar algo de pérdida por crash

  1. Sé explícito sobre la pérdida aceptable. “Algo” no es un número; define método de recuperación y ventana esperada.
  2. Mantén sync=standard y evita sync=disabled a menos que esté aislado. Si debes deshabilitar sync, hazlo en un dataset separado y déjalo obvio.
  3. Optimiza throughput del pool. Enfócate en layout de vdevs, estrategia ARC/L2ARC y comportamiento TXG en lugar del SLOG.
  4. Mantén el radio de impacto pequeño. Separa exports por clase de carga para que una opción arriesgada no infecte todo.

Plan C: Heredaste una plataforma NFS/ZFS misteriosa y nadie sabe qué es seguro

  1. Inventaría datasets y propiedades sync. Encuentra cualquier sync=disabled inmediatamente.
  2. Mapea exports a datasets. Asegura que cargas críticas no estén en un export “de rendimiento”.
  3. Muestra montajes cliente. Recolecta salidas de nfsstat -m de clientes representativos.
  4. Mide tasa de COMMIT y actividad SLOG. Determina si los clientes realmente demandan escrituras estables.
  5. Elige una postura por defecto de durabilidad. Mi sesgo: seguro por defecto para cargas stateful; hacks de rendimiento deben ser opt-in y aislados.

Preguntas frecuentes

1) Si pongo sync=standard, ¿las escrituras NFS son durables?

Son durables solo cuando el cliente/app solicita semánticas síncronas (escritura estable / comportamiento COMMIT). Si el cliente bufferiza y retrasa la estabilidad,
ZFS también bufferizará con gusto. Para durabilidad estricta independiente del cliente, usa sync=always en ese dataset.

2) ¿Cambia la durabilidad hard vs soft en NFS?

No directamente. Cambia el comportamiento ante fallos. hard tiende a colgar y reintentar, lo cual suele ser correcto para integridad de datos; soft
puede devolver errores que las apps manejan mal. Pero la durabilidad es sobre todo acerca de escrituras estables/COMMIT y la política sync del servidor.

3) ¿Agregar un SLOG siempre es la decisión correcta para NFS?

Solo si tienes una tasa significativa de escrituras síncronas y la latencia del pool es el cuello de botella. Si los clientes no emiten escrituras estables/COMMIT con frecuencia,
un SLOG no ayudará. Un SLOG malo puede perjudicar mucho.

4) ¿Cuál es la diferencia entre ZIL y SLOG otra vez?

ZIL es el mecanismo (registro de intención para operaciones síncronas). SLOG es un dispositivo dedicado donde ZFS coloca ese log para reducir latencia. Sin SLOG,
el ZIL vive en el pool principal.

5) Si usamos sync=disabled, ¿podemos aún estar “mayormente seguros”?

Puedes estar “mayormente afortunado”. sync=disabled reconoce escrituras sync sin hacerlas estables. Tras un crash, puedes perder operaciones recientes reconocidas,
incluyendo actualizaciones de metadatos. Úsalo solo para datasets aislados donde aceptes realmente ese riesgo.

6) ¿NFSv4 garantiza mejor consistencia ante crash que NFSv3?

No como afirmación general. NFSv4 añade características stateful y puede mejorar ciertos comportamientos, pero la consistencia ante crash sigue dependiendo de la política del cliente,
disciplina fsync de la app y almacenamiento estable honesto en el servidor.

7) ¿Por qué algunos clientes muestran picos de latencia enormes cuando una base de datos hace checkpoint?

Los checkpoints suelen desencadenar ráfagas de fsync/commit. Si el dataset es sync=always o la app usa O_DSYNC, ZFS enviará eso por ZIL/SLOG.
Si el SLOG es débil o está saturado, la latencia p99 explota precisamente en el momento del checkpoint.

8) ¿Debería espejar el SLOG?

Espejar un SLOG es por disponibilidad, no por integridad de datos en el sentido usual. Perder un dispositivo SLOG puede causar problemas en el pool o forzar
retrocesos inseguros dependiendo del comportamiento de la plataforma. Para sistemas que deben permanecer en línea, espejar SLOG suele valer la pena.

9) ¿Puedo “forzar a los clientes a ser seguros” desde el servidor?

No puedes obligar a una app a llamar fsync, pero puedes forzar a ZFS a tratar todas las escrituras como sync con sync=always. Eso reduce la dependencia
de la disciplina de flush del cliente. Es la herramienta práctica más potente que tienes en el lado servidor.

10) ¿Cuál es la forma más fiable de validar reclamos de durabilidad?

Pruebas controladas de crash con el SO cliente real, opciones de montaje reales y la aplicación real. Mide lo que la app cree que está comprometido, luego haz crash/reboot
y verifica invariantes. Los benchmarks no dicen la verdad aquí; las pruebas de crash sí.

Próximos pasos que puedes hacer esta semana

  1. Inventaria ajustes de sync: ejecuta zfs get -r sync en tus árboles NFS y marca cualquier sync=disabled.
  2. Elige dos exports críticos y estandariza perfiles de montaje cliente; recolecta nfsstat -m de clientes representativos.
  3. Mide proporciones COMMIT/write en el servidor durante picos; compáralas con lo que las aplicaciones afirman.
  4. Valida tu SLOG midiendo latencia y confirmando que tiene PLP; si no puedes probarlo, trátalo como sospechoso.
  5. Separa datasets por clase de durabilidad para que la optimización de un equipo no reescriba el modelo de riesgo de otro.
  6. Ejecuta una prueba de crash en staging: una carga que haga “commits”, un reboot forzado y un paso de verificación. Es sorprendente lo rápido que mueren los mitos.

El objetivo no es hacer que NFS sea “seguro” en lo abstracto. El objetivo es hacer que la seguridad sea una propiedad que puedas explicar, medir y aplicar—sin depender
de los valores por defecto de los clientes de hoy.

← Anterior
Docker “Text file busy” durante el despliegue: la solución que evita reinicios intermitentes
Siguiente →
ZFS Raw Send: replicación de datos cifrados sin compartir claves

Deja un comentario