Explicación del write hole en ZFS: quién lo sufre y por qué ZFS lo evita

¿Te fue útil?

No notas el write hole cuando todo está en verde. Lo notas cuando un índice de base de datos no se reconstruye, el sistema de archivos de una VM no se monta,
o el tarball de un cliente falla la verificación de checksum—meses después del “parpadeo” de energía.

El write hole es el equivalente en almacenamiento a una declaración de testigo que falta: el sistema “recuerda” algunas partes de una escritura, olvida otras,
y con confianza te entrega una historia que nunca ocurrió. ZFS es famoso por no hacer eso. Esto es por qué.

El write hole: qué es (y qué no es)

El clásico “write hole” es una falla de consistencia de paridad RAID causada por una escritura interrumpida. No es “el disco murió”, ni “el controlador explotó.”
Es más sutil: una pérdida de energía, kernel panic, reinicio del controlador, un solapamiento de cable, o un fallo de firmware golpea en medio de la actualización de una franja.
Los bloques de datos y los de paridad ya no coinciden.

El RAID por paridad (piensa en RAID5/RAID6 y sus variantes) mantiene redundancia calculando paridad sobre un conjunto de bloques en una franja. Para una franja RAID5 simple,
la paridad es un XOR de los bloques de datos. Si actualizas un bloque de datos, también debes actualizar la paridad. Eso significa múltiples escrituras por cada escritura lógica.
Si el sistema se cae después de que una de ellas llegue al disco pero antes que la otra, habrás creado una franja que parece válida estructuralmente pero es
matemáticamente inconsistente.

Esa inconsistencia no siempre se detecta de inmediato. De hecho, ese es todo el problema: el array sigue arrancando, el sistema de archivos sigue montando,
y la corrupción permanece como una mina hasta que lees el bloque “equivocado” (o hasta que necesitas reconstruir tras la falla de un disco).

Qué NO es el write hole:

  • No es lo mismo que bit rot (errores de medio con el tiempo). El bit rot puede ocurrir sin crashes; el write hole necesita una ruta de escritura interrumpida.
  • No significa “tu RAID es inútil.” Es un modo de falla específico disparado por actualizaciones parciales en sistemas por paridad.
  • No se arregla solo con “más paridad”. RAID6 reduce el riesgo en la reconstrucción, pero las actualizaciones parciales de paridad aún pueden crear franjas inconsistentes.

La pregunta clave es sencilla: cuando escribes, ¿puedes terminar con una mezcla de datos/paridad antiguos y nuevos que el sistema no puede reconciliar después?
Si la respuesta es sí, tienes riesgo de write hole.

Quién sufre el write hole: niveles RAID y stacks vulnerables

El write hole se asocia más comúnmente con RAID5, pero la clasificación real es: redundancia basada en paridad donde una única escritura lógica se convierte
en múltiples escrituras en disco, y la capa no puede garantizar atomicidad entre ellas.

Probablemente vulnerable (a menos que esté mitigado)

  • RAID5 / RAID6 en muchos stacks de RAID por software y controladores RAID por hardware, especialmente sin caché de escritura respaldada por batería o persistente.
  • mdadm RAID5/6 en Linux puede ser vulnerable si barriers/flushes están mal configurados, la caché miente, o ocurre un crash durante la actualización.
  • Sistemas de archivos clásicos sobre RAID por paridad (ext4, XFS, NTFS, etc.) generalmente no conocen el estado de la paridad de la franja. Asumen que el dispositivo de bloques es honesto.
  • Appliances de almacenamiento que “optimizaron” reordenando escrituras sin integridad de extremo a extremo pueden empeorarlo.

Menos vulnerable por diseño

  • Réplicas (RAID1, RAID10): una escritura se duplica; aún puedes tener torn writes a nivel de bloque, pero típicamente no envenenarás las matemáticas de paridad.
  • ZFS en espejos: añade checksums + semántica copy-on-write, así que no devolverá un bloque partido como “bueno”.
  • ZFS RAIDZ: la paridad forma parte de un esquema transaccional copy-on-write; evita el comportamiento clásico del write hole.

“Pero mi controlador tiene caché”

Una caché ayuda si es persistente ante pérdida de energía y si el controlador honra correctamente los flushes y el orden de escrituras.
Battery-backed write cache (BBWC) o flash-backed write cache (FBWC) pueden reducir drásticamente el riesgo.
“Write-back cache sin persistencia” es solo velocidad con un hábito de apuestas.

Broma #1: Un controlador RAID sin una caché realmente protegida contra pérdida de energía es como un paracaídas hecho de optimismo—solo descubres que es falso una vez.

Por qué ocurre: franjas parciales, paridad y “torn writes”

Considera un array RAID5 con tres discos: dos bloques de datos y uno de paridad por franja. Actualiza un bloque de 4K en una franja.
El array debe actualizar la paridad para que ésta siga coincidiendo con los dos bloques de datos.

Hay dos maneras clásicas de actualizar la paridad:

  • Read-modify-write (RMW): leer el bloque de datos antiguo y la paridad antigua, calcular la nueva paridad = old_parity XOR old_data XOR new_data, luego escribir nuevos datos y nueva paridad.
  • Reconstruct-write: leer todos los demás bloques de datos en la franja, calcular la paridad desde cero, escribir los datos actualizados y la nueva paridad.

De cualquier modo, ocurren múltiples lecturas y múltiples escrituras. Eso está bien cuando el sistema completa la secuencia. El write hole aparece cuando no lo hace.
Lo desagradable es que un crash puede interrumpir en muchos puntos:

  • Se escriben datos nuevos, la paridad sigue siendo antigua.
  • Se escribe la paridad, los datos siguen siendo antiguos.
  • Se actualiza medio sector (“torn write”), dependiendo de la semántica del dispositivo y del comportamiento de la caché.
  • Escrituras reordenadas por la caché/controlador/firmware del disco, pese a lo que cree el SO.

Tras el reinicio, el array no recuerda qué estaba haciendo en ese vuelo interrumpido. Una franja es solo una franja.
No existe un registro de transacciones duradero en la capa RAID que diga “esta actualización de paridad estaba en progreso; por favor reconciliar.”
Muchos stacks simplemente confían en la paridad y siguen adelante.

El peor momento para descubrir una franja inconsistente es durante una reconstrucción, cuando pierdes un disco y la paridad es ahora tu fuente de verdad.
Si la paridad está incorrecta, la reconstrucción produce basura, y la produce con confianza.

Por qué ZFS lo evita: COW, checksums y commits transaccionales

ZFS no “derrota” la física de forma mágica. Usa un modelo de almacenamiento que hace que el escenario clásico del write hole sea estructuralmente difícil de crear y
fácil de detectar. Tres pilares importan: copy-on-write, checksums de extremo a extremo y semántica de commit transaccional.

1) Copy-on-write (COW): nunca sobrescribir datos vivos in situ

Los sistemas de archivos tradicionales muchas veces sobrescriben metadatos y bloques de datos in situ (con journaling reduciendo, no eliminando, algunos riesgos).
ZFS toma otro enfoque: cuando modifica un bloque, asigna un bloque nuevo en otro lugar, escribe el nuevo contenido allí y solo entonces
actualiza punteros para referenciar el nuevo bloque.

Esa actualización de punteros también se realiza vía COW, caminando por el árbol hasta que el metadato de nivel superior (el uberblock) se actualiza.
La versión antigua permanece intacta en disco hasta que la nueva versión se confirma. Si te cae un crash a mitad de la escritura, normalmente vuelves a la versión anterior
consistente en disco. No terminas con “mitad antiguo, mitad nuevo” en metadatos que fingen ser un sistema de archivos coherente.

2) Checksums de extremo a extremo: detectar mentiras y corrupciones

ZFS almacena un checksum para cada bloque en sus metadatos padre. Cuando lees un bloque, ZFS verifica el checksum. Si no coincide,
ZFS sabe que el bloque está mal. Esto no es “puede estar mal.” Es inconsistencia matemática o criptográfica.

En vdevs redundantes (espejos, RAIDZ), ZFS puede entonces reparar: lee una copia alternativa/reconstrucción de paridad, encuentra una versión que coincida
con el checksum esperado, devuelve datos buenos y opcionalmente sana la copia mala. El checksum hace la corrupción detectable; la redundancia la hace corregible.

3) Grupos de transacción (TXGs): cambios de estado del sistema de archivos casi atómicos

ZFS agrupa cambios en transaction groups. En memoria acumula datos y metadatos sucios, y luego periódicamente sincroniza un TXG a disco.
El “punto de commit” es un nuevo uberblock escrito a disco que apunta al nuevo árbol. Los uberblocks se escriben en un conjunto rotatorio para que ZFS pueda elegir
el más reciente válido al importar.

Esto importa para el write hole porque ZFS no necesita realizar actualizaciones en sitio de paridad de bloques existentes para “parchar” la verdad actual.
Escribe bloques nuevos, nueva paridad, y solo más tarde cambia el puntero raíz. Si se cae antes del cambio, los bloques nuevos quedan huérfanos
y luego se limpian; el estado antiguo y consistente sigue allí.

Donde vivía el write hole, ZFS pone una puerta con llave

Write hole clásico: “actualicé datos pero no la paridad, ahora la franja es inconsistente y nadie lo sabe.”
Modelo ZFS: “escribí datos+paridad nuevos en ubicaciones nuevas. Si no terminé, el árbol activo del sistema de archivos sigue apuntando a los bloques antiguos y consistentes.”

ZFS aún puede experimentar escrituras interrumpidas, pero tiene dos ventajas:

  • Consistencia del árbol activo: el estado en disco elegido al importar es una instantánea coherente del sistema de archivos.
  • Detectabilidad: si un bloque está corrupto, ZFS lo sabe vía checksum; no lo acepta silenciosamente como bueno.

Una cita útil para pegar en una nota junto al rack:

“La esperanza no es una estrategia.” — General Gordon R. Sullivan

Detalles de RAIDZ: ancho de franja variable y qué cambia

RAIDZ es la paridad RAID de ZFS, pero no es “RAID5 implementado dentro de ZFS” en el sentido ingenuo. RAIDZ está integrado en la asignación de ZFS y
en el modelo transaccional.

En el RAID5 clásico, una escritura lógica a un bloque pequeño a menudo fuerza un ciclo read-modify-write en una franja completa. Ahí es donde las actualizaciones de franjas parciales
se vuelven peligrosas, especialmente si el sistema sobrescribe bloques existentes in situ.

RAIDZ hace algo diferente: usa ancho de franja variable. ZFS puede empaquetar datos a través de discos basándose en el tamaño real de escritura y el diseño de bloques,
en lugar de forzar límites de franja fijos. Sigue teniendo paridad, pero la asignación se coordina con las actualizaciones COW.

La consecuencia operativa clave: la paridad de RAIDZ se calcula para los bloques recién asignados y se escribe como parte del sync del TXG.
No se está añadiendo retroactivamente paridad a franjas vivas existentes mediante actualizaciones in situ de la misma manera que hace el RMW clásico de RAID5.

Esto no significa que RAIDZ sea invencible. Puedes seguir perdiendo datos si pierdes más dispositivos de los que la paridad permite, o si tu sistema miente sobre flushes
y pierdes el(los) uberblock(s) comprometido(s) más reciente(s). Pero el fallo por defecto no es el clásico “desajuste silencioso de paridad creado a mitad de una escritura y luego usado como verdad”.

La misión secundaria del villano: caches, barreras y “juro que se escribió”

La discusión del write hole siempre arrastra caches porque la mayoría de corrupciones en el mundo real no son “bug de ZFS” o “bug de mdadm.” Son “la pila de almacenamiento
me dijo que estaba en almacenamiento estable, pero en realidad estaba en caché volátil.”

Tu SO usa flushes (FUA/flush/barriers) para decir: “confirma esto en medios no volátiles antes de confirmar.” Si el dispositivo/controlador ignora eso,
todo lo que está por encima opera en una línea temporal fantástica.

ZFS no es inmune a las mentiras. ZFS asume que cuando emite un flush y recibe un acuse, los datos son duraderos. Si tu hardware dice “claro” y luego pierde energía, puedes perder el(los) TXG(s) más reciente(s).
Normalmente eso significa perder las escrituras más recientes, no corromper datos comprometidos antiguos, pero el radio del daño depende de cuán deshonesto sea el dispositivo.

Aquí es donde SLOG, configuraciones de sync y la protección ante pérdida de energía entran en la conversación. Si pones sync=disabled para perseguir benchmarks, te estás
ofreciendo a redescubrir por qué las bases de datos insisten en fsync.

Broma #2: Poner sync=disabled en producción es como quitar el detector de humo porque pita cuando hay humo.

Datos interesantes y contexto histórico

  • Dato 1: El write hole se discutía en la literatura RAID mucho antes de los SSD; está ligado a la semántica de actualización de paridad, no a rarezas del flash.
  • Dato 2: Los proveedores de RAID por hardware impulsaron BBWC en parte porque convierte actualizaciones multi-escritura en operaciones “suficientemente atómicas” frente a pérdida de energía.
  • Dato 3: Arrays empresariales tempranos implementaron “logging de paridad” o journaling en la capa RAID para reconciliar actualizaciones incompletas de franjas tras un crash.
  • Dato 4: El journaling del sistema de archivos (p. ej., ext3/ext4) protege la consistencia de metadatos del FS, pero generalmente no puede arreglar un desajuste de franja de paridad debajo.
  • Dato 5: El checksumming de extremo a extremo de ZFS fue una reacción directa a la corrupción silenciosa en pilas de almacenamiento—controladores, firmware e incluso DMA pueden fallar.
  • Dato 6: El concepto de “uberblock” de ZFS provee múltiples puntos de commit recientes; al importar, ZFS puede escoger el más reciente válido.
  • Dato 7: La reputación de RAID5 empeoró conforme crecieron los tamaños de disco; las ventanas de reconstrucción se alargaron, aumentando la probabilidad de encontrar un error de lectura irrecuperable.
  • Dato 8: “Write hole” no solo ocurre por pérdida de energía; cualquier interrupción en vuelo (panic, reinicio de HBA, flap de enlace) puede producir el mismo resultado de actualización parcial.
  • Dato 9: Algunos SSD históricamente reconocían flushes demasiado pronto por bugs de firmware; los ingenieros de fiabilidad aprendieron a desconfiar de etiquetas “enterprise” baratas en flash.

Tres microhistorias del mundo corporativo

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

Una compañía SaaS mediana operaba su clúster principal de PostgreSQL sobre un volumen Linux software RAID6 con un HBA “reconocido” en modo IT.
La suposición era sencilla: RAID6 significa “seguro”, y ext4 con journaling significa “consistente”. Tenían UPS en el rack, así que los eventos de energía
se trataban como teóricos.

Luego vino un mantenimiento del edificio. La energía no cayó completamente; bajó, rebotó y los servidores se reiniciaron. Los logs del UPS mostraron una transferencia corta,
luego una segunda transferencia. Los hosts regresaron. La monitorización estaba en verde. Todo el mundo siguió con su trabajo.

Dos semanas después un disco falló—normal, reemplazable, aburrido. Durante la reconstrucción, mdadm empezó a lanzar errores de lectura que no coincidían con SMART.
Luego PostgreSQL comenzó a fallar la verificación de checksum en una tabla que no se había tocado desde el evento de energía. Los archivos de datos parecían “bien”,
hasta que no lo eran.

El postmortem encontró una probable inconsistencia de franja: algunas franjas tenían paridad que reflejaba bloques de datos nuevos, mientras otras tenían paridad antigua y datos nuevos.
El journal del sistema de archivos había hecho su trabajo, pero no tuvo oportunidad: se construyó sobre una capa de bloques que devolvía bloques corruptos como si fueran válidos.

La suposición equivocada no era “RAID6 es malo.” La suposición equivocada fue creer que la capa de bloques siempre preserva el orden de escrituras y atomicidad durante
una actualización de paridad. Migraron a ZFS en espejos para bases de datos y reservaron RAIDZ2 para almacenamiento de objetos grandes y secuenciales donde scrubs y checksums
les daban comportamiento detectable y reparable.

Microhistoria 2: La optimización que salió mal

Otra organización—plataforma de analítica interna, muchos jobs por lotes—corría OpenZFS en RAIDZ2. El rendimiento estaba bien, hasta que incorporaron una nueva carga:
un servicio de ingestión con cola que hacía pequeñas escrituras síncronas. La latencia subió.

Alguien hizo lo que siempre se hace: buscó en Google “ZFS slow sync writes”, vio la frase mágica sync=disabled, y la aplicó al dataset.
Los gráficos sonrieron. Los tickets se detuvieron. Aplausos.

Meses después tuvieron un kernel panic durante una actualización rutinaria de drivers. Al reiniciar, el pool se importó limpio. Sin errores. Pero el servicio de ingestión comenzó
a reenviar mensajes que creía comprometidos pero que en realidad no estaban en almacenamiento estable. El sistema abajo ingirió duplicados y luego sobrescribió tablas derivadas.
Nada explotó fuerte; simplemente quedó silenciosamente incorrecto.

El retroceso no fue corrupción de ZFS. Fue corrupción semántica: el contrato de durabilidad de la aplicación se rompió. Al deshabilitar sync, cambiaron
“ack tras commit durable” por “ack tras RAM sentirse bien”. La solución fue aburrida: restaurar sync=standard, añadir un dispositivo SLOG con protección ante pérdida de energía,
y ajustar el batching de la aplicación para que no fsynceara en cada estornudo.

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

Un equipo de servicios financieros corría ZFS en espejos para su datastore de VM y ZFS RAIDZ2 para backups. Su ingeniero de almacenamiento era agresivamente poco romántico:
scrubs mensuales, tests SMART, y alertas por incluso un solo error de checksum. También se negaban a comprar SSD sin clara protección contra pérdida de energía.

Un trimestre, un lote nuevo de discos comenzó a devolver errores de checksum ocasionales durante los scrubs. No errores de lectura. No fallos SMART. Solo desajustes de checksum
que ZFS corregía desde la redundancia. Los dashboards mostraban eventos de “self-healing” y una lenta subida en errores corregidos.

Procurement quería ignorarlo porque las cargas iban bien. El ingeniero insistió: el presupuesto de errores no es una cuenta de ahorros. Drenaron el pool
de ese lote en varias ventanas de mantenimiento, reemplazaron los discos y RMAaron la partida.

Dos meses después, otro equipo en el mismo edificio (stack de vendor diferente) tuvo un incidente feo: corrupción silenciosa apareció durante una prueba de restore.
El equipo financiero ni siquiera tuvo incidente. Su práctica fue aburrida, correcta y por tanto efectiva: los scrubs convirtieron la corrupción invisible en una tarea de mantenimiento programada en lugar de en una sorpresa.

Tareas prácticas: comandos, salidas y decisiones

Estas son acciones reales de operador. Cada tarea incluye un comando, qué significa la salida y qué decides después.
Los ejemplos asumen OpenZFS en Linux, pero la mayoría se traduce a illumos/FreeBSD con cambios menores.

Tarea 1: Confirmar salud del pool y si ya tienes errores de checksum

cr0x@server:~$ zpool status -v tank
  pool: tank
 state: ONLINE
status: One or more devices has experienced an unrecoverable error.
action: Determine if the device needs to be replaced, and clear the errors
  scan: scrub repaired 0B in 02:13:44 with 2 errors on Sun Dec 22 03:10:18 2025
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          raidz2-0                  ONLINE       0     0     0
            ata-WDC_WD80EFAX-00A0  ONLINE       0     0     2
            ata-WDC_WD80EFAX-00A1  ONLINE       0     0     0
            ata-WDC_WD80EFAX-00A2  ONLINE       0     0     0
            ata-WDC_WD80EFAX-00A3  ONLINE       0     0     0

errors: Permanent errors have been detected in the following files:

        tank/vmstore:vm-102-disk-0

Qué significa: ZFS detectó datos malos vía checksum y no pudo repararlos completamente (error permanente), o reparó pero aún registra archivos impactados.

Decisión: Tratar esto como un incidente de producción. Identificar la carga afectada, restaurar el archivo/volumen específico desde backup o réplica,
y planear reemplazo de disco si los errores siguen aumentando.

Tarea 2: Ver si tu layout de vdev es basado en paridad (donde importa el write hole clásico)

cr0x@server:~$ zpool status tank
  pool: tank
 state: ONLINE
  scan: none requested
config:

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          raidz2-0  ONLINE       0     0     0
            sda     ONLINE       0     0     0
            sdb     ONLINE       0     0     0
            sdc     ONLINE       0     0     0
            sdd     ONLINE       0     0     0

Qué significa: vdev parity RAIDZ2. ZFS evitará patrones clásicos de write hole, pero aún debes preocuparte por la honestidad de flushes y semánticas de sync.

Decisión: Si este pool aloja bases de datos con muchas escrituras síncronas, evalúa SLOG y configuraciones de dataset. Si es almacenamiento masivo, enfócate en la cadencia de scrubs y salud de discos.

Tarea 3: Confirmar política sync y si alguien “optimizó” la durabilidad

cr0x@server:~$ zfs get -o name,property,value -s local,default sync tank tank/db tank/vmstore
NAME         PROPERTY  VALUE
tank         sync      standard
tank/db      sync      standard
tank/vmstore sync      disabled

Qué significa: tank/vmstore está reconociendo escrituras síncronas sin garantizar almacenamiento estable.

Decisión: Si esto aloja VMs, bases de datos o cualquier cosa que espere que fsync signifique algo, revierte a sync=standard.
Si mantienes disabled, documenta la ventana de pérdida de datos y consigue aprobación explícita.

Tarea 4: Comprobar si realmente tienes un SLOG y qué es

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

        NAME           STATE     READ WRITE CKSUM
        tank           ONLINE       0     0     0
          raidz2-0     ONLINE       0     0     0
            sda        ONLINE       0     0     0
            sdb        ONLINE       0     0     0
            sdc        ONLINE       0     0     0
            sdd        ONLINE       0     0     0
        logs
          nvme0n1p2    ONLINE       0     0     0

Qué significa: Existe un dispositivo de log separado. Puede acelerar escrituras síncronas si tiene protección ante pérdida de energía y baja latencia.

Decisión: Valida que el NVMe sea de clase enterprise con PLP. Si no, podrías estar intercambiando latencia por integridad bajo pérdida de energía.

Tarea 5: Confirmar ashift (la desalineación puede aparentar “paridad ZFS lenta”)

cr0x@server:~$ zdb -C tank | grep -E "ashift|vdev_tree" -n | head
35:        vdev_tree:
52:                ashift: 12
89:                ashift: 12
126:               ashift: 12

Qué significa: ashift=12 = sectores de 4K. Buen valor por defecto para discos/SSDs modernos.

Decisión: Si ves ashift=9 en discos 4K, espera problemas. Arreglarlo requiere recrear el vdev (no hay cambio in-place).

Tarea 6: Comprobar recordsize/volblocksize contra la carga (escrituras pequeñas + RAIDZ pueden morder)

cr0x@server:~$ zfs get -o name,property,value recordsize,volblocksize tank/db tank/vmstore tank/vmstore/vm-102
NAME                  PROPERTY      VALUE
tank/db               recordsize    128K
tank/vmstore          recordsize    128K
tank/vmstore/vm-102   volblocksize  8K

Qué significa: Datasets usan recordsize. ZVOLs usan volblocksize. El desajuste puede causar amplificación de escritura.

Decisión: Para zvols de VM, elige un volblocksize que coincida con el guest y la carga (a menudo 8K o 16K). Para bases de datos en datasets,
considera recordsize más pequeño si IO es aleatorio y pequeño, pero mide antes de cambiar.

Tarea 7: Observar IO en tiempo real y detectar si estás limitado por sync

cr0x@server:~$ zpool iostat -v tank 1 5
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                        3.21T  11.3T    112    980  22.4M  18.1M
  raidz2-0                  3.21T  11.3T    112    980  22.4M  18.1M
    sda                         -      -     18    160  3.7M   4.2M
    sdb                         -      -     19    165  3.8M   4.3M
    sdc                         -      -     18    162  3.7M   4.2M
    sdd                         -      -     18    158  3.6M   4.1M
  nvme0n1p2                      -      -      0    510  0     6.1M
--------------------------  -----  -----  -----  -----  -----  -----

Qué significa: El dispositivo de log está recibiendo muchas escrituras. Eso es consistente con una carga pesada en sync siendo absorbida por el SLOG.

Decisión: Si la latencia sigue siendo mala, el SLOG puede ser lento o estar saturado. Considera un NVMe PLP más rápido, o reducir la frecuencia de sync en la capa de la app (batching).

Tarea 8: Confirmar comportamiento de sync de TXG y si estás thrashing en syncs

cr0x@server:~$ grep -E "txg_sync|spa_sync" /proc/spl/kstat/zfs/* 2>/dev/null | head
/proc/spl/kstat/zfs/dbgmsg:txg_sync_start txg 89213
/proc/spl/kstat/zfs/dbgmsg:spa_sync: syncing tank txg 89213
/proc/spl/kstat/zfs/dbgmsg:txg_sync_done txg 89213

Qué significa: Puedes ver ciclos de sync. Syncs frecuentes bajo carga pueden significar escrituras pequeñas forzando commits.

Decisión: Correlaciona con patrones fsync de la aplicación. Si es una BD, ajusta su checkpointing/batching; si es NFS, verifica opciones de montaje y semánticas sync del cliente.

Tarea 9: Validar ajustes de caché de escritura del disco (y decidir si desactivar caches volátiles)

cr0x@server:~$ sudo hdparm -W /dev/sda
/dev/sda:
 write-caching =  1 (on)

Qué significa: La caché de escritura del disco está activada. Eso no es automáticamente malo si los flushes se honran y tienes protección ante pérdida de energía.

Decisión: Si no confías en la plataforma (sin PLP, expanders SATA dudosos, historial de bugs en flush), considera desactivar la caché de escritura:
hdparm -W0 (con pruebas). Espera impacto en rendimiento.

Tarea 10: Confirmar TRIM/autotrim para pools SSD (no es write hole, pero evita degradación)

cr0x@server:~$ zpool get autotrim tank
NAME  PROPERTY  VALUE     SOURCE
tank  autotrim  on        local

Qué significa: ZFS emitirá TRIM automáticamente. Ayuda al rendimiento sostenido y al desgaste de SSDs.

Decisión: Manténlo activado para SSDs a menos que tengas un dispositivo que se comporte mal con TRIM (raro hoy, común en los malos tiempos).

Tarea 11: Ejecutar un scrub y entender qué significa “repaired” vs “errors”

cr0x@server:~$ sudo zpool scrub tank
cr0x@server:~$ zpool status tank
  pool: tank
 state: ONLINE
  scan: scrub in progress since Thu Dec 26 01:12:40 2025
        512G scanned at 1.21G/s, 96G issued at 233M/s, 3.21T total
        0B repaired, 2.99% done, 0:21:34 to go
config:

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          raidz2-0  ONLINE       0     0     0
            sda     ONLINE       0     0     0
            sdb     ONLINE       0     0     0
            sdc     ONLINE       0     0     0
            sdd     ONLINE       0     0     0

errors: No known data errors

Qué significa: El scrub está leyendo y verificando checksums. “0B repaired” es bueno. “No known data errors” es lo que quieres ver.

Decisión: Si los scrubs reparan repetidamente datos, tienes un problema de fiabilidad (discos, cables, backplane, HBA, memoria).
Si los scrubs encuentran errores permanentes, inicia restauración/recuperación para los datasets afectados e investiga hardware.

Tarea 12: Verificar compresión y su efecto en patrones de IO

cr0x@server:~$ zfs get -o name,property,value compression,compressratio tank/db
NAME     PROPERTY       VALUE
tank/db  compression    lz4
tank/db  compressratio  1.78x

Qué significa: La compresión está activada y es efectiva. Eso reduce escrituras físicas para el mismo volumen lógico de escrituras.

Decisión: Normalmente mantener lz4 activado. Si la latencia la domina la CPU (raro en CPUs modernas), mide antes de cambiar nada.

Tarea 13: Comprobar comportamiento del ARC (porque la gente culpa “write hole ZFS” cuando en realidad es presión de memoria)

cr0x@server:~$ arcstat 1 3
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
01:22:10   845    47      5    12   26    35   74     0    0   58G   64G
01:22:11   812    55      6    14   25    41   75     0    0   58G   64G
01:22:12   901    50      5    13   26    37   74     0    0   58G   64G

Qué significa: Baja tasa de misses, ARC cerca del objetivo. Las lecturas se sirven principalmente desde caché.

Decisión: Si miss% es alto y los discos están ocupados, considera más RAM o L2ARC (con cuidado). Pero no uses L2ARC como sustituto para arreglar un cuello de botella de escrituras sync.

Tarea 14: Detectar un dispositivo mentiroso: confirmar caché de escritura, soporte de flush y mensajes del kernel

cr0x@server:~$ dmesg | grep -iE "flush|fua|barrier|cache" | tail -n 8
[    2.913244] sd 0:0:0:0: [sda] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA
[    2.914881] sd 0:0:0:1: [sdb] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA
[    2.916402] sd 0:0:0:2: [sdc] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA
[    2.918103] sd 0:0:0:3: [sdd] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA

Qué significa: “doesn’t support DPO or FUA” no es automáticamente fatal (los flushes aún pueden funcionar), pero es una pista:
la pila puede estar confiando en flushes en lugar de en semánticas force-unit-access.

Decisión: Si este es un pool crítico y con muchas escrituras síncronas, prefiere dispositivos/HBAs que se comporten de forma predecible con flushes y que tengan PLP donde corresponda.
Considera espejos para bases de datos.

Guion de diagnóstico rápido

Cuando alguien dice “ZFS está corrupto” o “tuvimos el write hole”, tu trabajo es separar tres cosas:
(1) fallas de integridad de datos, (2) semánticas de durabilidad, y (3) cuellos de botella de rendimiento. Rápido.

Primero: determina si tienes errores de datos ahora mismo

  1. Revisa la salud del pool: zpool status -v. Si ves errores de checksum o errores permanentes, trátalo como incidente activo.
  2. Revisa scrubs recientes: en la línea scan de zpool status. Si los scrubs repararon datos recientemente, localiza la fuente de corrupción.
  3. Revisa logs del sistema: dmesg para resets de enlace, errores I/O, timeouts de HBA. Si el transporte es inestable, todo lo que está arriba es víctima.

Segundo: confirma si la queja es realmente por escrituras confirmadas que se perdieron

  1. Revisa settings de sync del dataset: zfs get sync. Si ves disabled, probablemente tienes una violación de contrato de durabilidad, no corrupción.
  2. Revisa presencia/salud del SLOG: sección logs de zpool status. Si falta y la carga es heavy en sync, la latencia puede ser esperada.
  3. Pregunta qué significa “perdido”: ¿La app hizo ack tras fsync? Si es así, investiga sync, PLP y honestidad de flushes.

Tercero: encuentra el cuello de botella (CPU, disco, sync, fragmentación o capacidad)

  1. Vista IO: zpool iostat -v 1 para ver si discos o SLOG están saturados.
  2. Presión de capacidad: zfs list y porcentaje de asignación del pool. Pools casi llenos fragmentan y ralentizan escrituras.
  3. Desajuste de carga: escrituras aleatorias pequeñas en RAIDZ sin tuning pueden ser lentas; considera espejos para datasets sensibles a latencia.

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

1) “Tuvimos un reboot y ahora la base de datos está inconsistente”

  • Síntomas: BD reporta transacciones recientes faltantes; la app reenvía mensajes; sin errores de checksum en ZFS.
  • Causa raíz: sync=disabled (o la aplicación confiaba en fsync pero la pila no lo proporcionó), o caché volátil mintió.
  • Solución: Poner sync=standard; añadir SLOG con PLP si hace falta; validar UPS y probar comportamiento ante pérdida de energía; asegurar que el hardware honra flushes.

2) “El scrub sigue reparando datos cada mes”

  • Síntomas: zpool status muestra bytes reparados o contadores de checksum crecientes; las cargas parecen bien.
  • Causa raíz: Corrupción silenciosa por discos, cableado, backplane, HBA, firmware, o memoria (sí, la RAM importa).
  • Solución: Reemplazar discos sospechosos; volver a asentar/reemplazar cables; actualizar firmware de HBA; ejecutar memtest; mantener scrubs; no ignorar errores corregidos.

3) “RAIDZ es lento para almacenamiento de VMs”

  • Síntomas: Alta latencia, especialmente en escrituras sync; zpool iostat muestra muchas escrituras pequeñas; CPU mayormente inactiva.
  • Causa raíz: Carga de pequeñas escrituras aleatorias; RAID por paridad tiene amplificación de escritura; desajuste volblocksize/recordsize; sin SLOG para patrones sync-heavy.
  • Solución: Usar espejos para VM/bases de datos; ajustar volblocksize; añadir SLOG PLP para sync; reservar RAIDZ para cargas de throughput.

4) “Vemos errores permanentes en algunos archivos tras un crash”

  • Síntomas: zpool status -v lista archivos específicos con errores permanentes; errores de checksum en un disco.
  • Causa raíz: Corrupción real en disco que la redundancia no pudo sanar (p. ej., paridad excedida, sectores malos en múltiples discos, o réplicas obsoletas).
  • Solución: Restaurar archivos/volúmenes afectados desde backup/snapshot/replicación; reemplazar hardware defectuoso; correr scrub; validar backups con pruebas de restauración.

5) “Tras añadir un caché SSD, las cosas empeoraron”

  • Síntomas: Picos de latencia; SSD al 100% ocupado; poca mejora en hit rate.
  • Causa raíz: L2ARC mal usado o SSD débil; cacheando la carga equivocada; presión de memoria por overhead de metadata de L2ARC.
  • Solución: Quitar o redimensionar L2ARC; añadir RAM primero; enfocarse en SLOG si el problema son escrituras sync, no lecturas.

Listas de verificación / plan paso a paso

Checklist: diseñar ZFS sin reintroducir dolor tipo write hole

  1. Elige vdevs según necesidades de latencia: espejos para bases de datos/VMs; RAIDZ2/3 para almacenamiento masivo y backups.
  2. Asume que ocurren eventos de energía: UPS ayuda, pero prueba el comportamiento. Brownouts y doble-transferencia son reales.
  3. No deshabilites sync a la ligera: si lo haces, aíslalo a un dataset y documenta el riesgo.
  4. Usa dispositivos PLP donde la durabilidad importe: especialmente para SLOG y cargas con muchos metadatos.
  5. Programa scrubs: mensual es común; más frecuente para pools críticos con mucho churn de datos.
  6. Alerta por errores de checksum: errores corregidos son una advertencia temprana, no una historia de éxito.
  7. Valida backups con pruebas de restauración: “backup exitoso” no es “restore funciona”.
  8. Mantén margen libre: evita pools al límite; fragmentación y presión de asignación te castigarán.

Paso a paso: investigar alegaciones de “posible write hole” en un stack por paridad

  1. Recopila la línea temporal: qué se cayó, cuándo y qué estaba escribiendo en ese momento.
  2. Busca síntomas de escritura parcial: inconsistencias a nivel de app sin errores de lectura en disco.
  3. Revisa caches volátiles: caché de escritura del disco, modo de caché del controlador RAID, falta de BBWC/FBWC.
  4. Valida semánticas de flush: logs del kernel; ajustes del controlador; capas de virtualización.
  5. Ejecuta chequeos de consistencia: para ZFS corre scrub; para mdadm considera check/repair (con cuidado); para bases de datos corre checksums internos.
  6. Decide remediación: restaurar copias conocidas buenas, reconstruir desde réplicas, o migrar a un diseño que ofrezca integridad de extremo a extremo.

Preguntas frecuentes

1) ¿ZFS elimina completamente el write hole?

ZFS evita el clásico write hole de RAID por paridad al no hacer actualizaciones in situ y al confirmar cambios de forma transaccional.
Pero ZFS aún depende de que el hardware honre las semánticas de flush. Si los dispositivos mienten, puedes perder escrituras recientes.

2) ¿RAIDZ es solo RAID5 con otro nombre?

No. RAIDZ está integrado con la asignación de ZFS y el comportamiento copy-on-write, soporta ancho de franja variable y está protegido por checksums de extremo a extremo.
Se comporta diferente bajo condiciones de escritura parcial que los patrones clásicos RMW de RAID5.

3) Si uso RAIDZ2/3, ¿puedo saltarme los backups?

No. La paridad protege contra fallo de dispositivos, no contra borrados, ransomware, bugs de aplicación o “ups escribí el dataset equivocado.”
Los snapshots de ZFS ayudan, pero no son backups fuera del sistema a menos que se repliquen fuera.

4) ¿Por qué se recomiendan espejos para bases de datos?

Por latencia. Los espejos tienen rutas de escritura más simples y a menudo mejor IOPS para cargas aleatorias. RAIDZ es excelente en eficiencia de capacidad y throughput,
pero la matemática de paridad más la amplificación de escritura puede perjudicar escrituras pequeñas y síncronas.

5) ¿Necesito un dispositivo SLOG?

Solo si tienes escrituras síncronas significativas y los dispositivos del pool son demasiado lentos para alcanzar los objetivos de latencia.
Un SLOG no es una caché de escritura general; acelera el ZIL para escrituras sync. No añadas un SSD barato sin PLP y lo llames “más seguro.”

6) ¿Qué significa “checksum error” en zpool status?

ZFS leyó datos que no coincidían con el checksum almacenado en metadatos. Eso es prueba de corrupción en la ruta de lectura o en el medio.
Con redundancia, ZFS a menudo puede auto-reparar leyendo una copia buena.

7) ¿El journaling de ext4 puede prevenir corrupción de RAID5 por write hole?

El journaling de ext4 puede mantener metadatos del sistema de archivos consistentes tras un crash, pero no puede corregir un desajuste de franja de paridad debajo.
Asume que el dispositivo de bloques provee bloques coherentes cuando se los solicita.

8) ¿Un UPS no resuelve el write hole?

El UPS reduce el riesgo pero no lo elimina. Crashes, kernel panics, reinicios de controlador y bugs de firmware aún interrumpen secuencias de escritura.
Además, muchos “eventos de energía” son brownouts y reboots, no pérdidas limpias de energía.

9) Si ZFS es seguro, ¿por qué la gente sigue perdiendo datos?

Porque la seguridad es una propiedad del sistema. Culpables comunes: quedarse sin redundancia, hardware malo, caches que mienten, desactivar sync,
ignorar errores de checksum y no probar restauraciones.

10) ¿Cuál es el hábito operativo más simple que mejora la integridad en ZFS?

Scrubs regulares con alertas ante cualquier error de checksum. El scrub convierte la corrupción silenciosa en un ticket que puedes manejar en un día laborable.

Siguientes pasos prácticos

Si estás corriendo RAID por paridad fuera de ZFS, asume que el write hole existe hasta que demuestres lo contrario. Si usas ZFS, asume que tu hardware
aún puede mentir y que tus operadores pueden “optimizar” la durabilidad fuera del sistema. Ambas suposiciones son saludables.

  1. Inventario de riesgo: identifica arrays por paridad, modos de caché y si la caché de escritura es persistente.
  2. Audita propiedades ZFS: encuentra datasets con sync=disabled y decide si realmente lo querías.
  3. Cadencia de scrubs + alertas: asegura que los scrubs se ejecuten y que alguien reciba paginación por errores de checksum.
  4. Alinea diseño y carga: espejos para latencia, RAIDZ para capacidad/throughput, y no pretendas que sean intercambiables.
  5. Prueba un escenario de pérdida de energía: no tirando del plug en producción, sino validando que tu plataforma honra flushes y que tus apps toleran recovery tras crash.
← Anterior
MySQL vs PostgreSQL: simulacros de PITR—prueba la restauración antes de necesitarla
Siguiente →
Firewall de Proxmox te bloqueó: restaurar SSH/Web UI desde la consola sin pánico

Deja un comentario