Tu pool tiene mucho “throughput” en el papel. Sin embargo la base de datos se queda atascada con pequeñas actualizaciones, el host de VM reporta picos de latencia en almacenamiento y el p99 de tu aplicación pasa de “bien” a “por qué me llama el CEO”. Esta es la clásica trampa de ZFS con escrituras aleatorias pequeñas: la carga está limitada por IOPS y latencia, mientras tu diseño está optimizado para ancho de banda.
Cuando la gente dice “ZFS es lento”, normalmente quieren decir “construí vdevs de paridad y luego les pedí que se comportaran como espejos con escrituras aleatorias pequeñas”. ZFS no te traicionó. La física no. Y las matemáticas de la paridad se unieron al proyecto grupal.
Qué significan realmente las “escrituras aleatorias pequeñas” en términos de ZFS
Las escrituras aleatorias pequeñas son la carga que castiga diseños pensados para ancho de banda secuencial. Piensa en escrituras de 4K–32K dispersas por un conjunto de trabajo grande, típicamente bajo concurrencia. Bases de datos, imágenes de VM, sistemas de archivos de sobreposición de contenedores, colas de correo y árboles de archivos con mucha metadata viven aquí.
Para ZFS, “pequeño” y “aleatorio” no es solo una característica de la aplicación. También es una característica de diseño y de transacción. ZFS es copy-on-write (CoW). Eso significa que sobrescribir un bloque existente no es una actualización en el lugar; es “asignar nuevos bloques, escribirlos y luego actualizar la metadata para apuntar a ellos”. Así que la E/S real de escritura con frecuencia es más que “una escritura de 8K”.
Ahora añade vdevs de paridad (RAIDZ1/2/3). Cada escritura puede necesitar tocar múltiples discos además de cálculo de paridad, a veces requiriendo patrones de lectura-modificación-escritura si la escritura no está alineada con los límites completos de stripe. Ahí es donde se van a juzgar tus IOPS y latencias.
Dos definiciones que debes mantener separadas
- Limitado por IOPS: el throughput es bajo porque cada IO es pequeño y cada uno te cuesta latencia. Más ancho de banda no ayudará.
- Limitado por latencia: tu profundidad de cola crece porque cada operación tarda demasiado en completarse; las aplicaciones hacen timeouts, los logs muestran picos y “disco ocupado” resulta ser engañoso.
Espejos y arreglos de paridad pueden ofrecer mucho espacio bruto y ancho de banda secuencial aceptable. Solo uno de ellos suele entregar latencia de escrituras aleatorias pequeñas predecible sin ajustes heroicos: los espejos.
Espejos vs paridad en una frase (y la frase larga que realmente necesitas)
Una frase: Los espejos suelen vencer a los vdevs de paridad en escrituras aleatorias pequeñas porque una escritura en espejo es “escribir en dos lugares”, mientras que la paridad suele ser “leer algunas cosas, calcular paridad, escribir varias cosas”, con más operaciones de E/S y más espera.
La frase larga que realmente necesitas: Los vdevs de paridad de ZFS rinden bien cuando las escrituras son grandes, alineadas y en streaming, pero degradan bruscamente cuando las escrituras son pequeñas, fragmentadas o con muchas sync, porque amplifican la E/S, aumentan la latencia por operación y reducen la flexibilidad del scheduler a nivel de vdev.
La ruta de escritura en paridad: donde tus IOPS se jubilan prematuramente
La paridad (RAIDZ) es excelente si quieres eficiencia de capacidad y tolerancia a fallos. No es buena si buscas escrituras aleatorias de baja latencia. La razón no es mística; es aritmética y mecánica.
Escrituras de paridad y el problema del “full-stripe”
En un vdev RAIDZ, los datos se stripean a través de los discos con paridad. Una escritura de stripe completo significa que estás escribiendo un conjunto completo de columnas de datos más las columnas de paridad para una stripe. Si puedes hacer escrituras de stripe completo consistentemente, la paridad es bastante eficiente: no hace falta leer datos viejos para recomputar la paridad; ya tienes todos los datos nuevos.
Las escrituras aleatorias pequeñas usualmente no son stripe completo. Son parcial-stripe y con frecuencia desalineadas respecto al recordsize del vdev, al tamaño de sector (ashift) y a la geometría RAIDZ. Cuando haces una actualización parcial de stripe, la paridad puede requerir una lectura-modificación-escritura:
- Leer bloques de datos antiguos que forman parte de la stripe (o paridad) para calcular el delta
- Calcular la nueva paridad
- Escribir los nuevos bloques de datos y los nuevos bloques de paridad
Eso significa que una pequeña escritura lógica puede convertirse en varias E/S físicas a través de múltiples discos. Cada disco tiene su propia latencia. Tu operación termina cuando la I/O más lenta requerida se completa.
Por qué esto perjudica más con HDDs
Con HDDs, los IOPS aleatorios están limitados por seek y latencia rotacional. Si tu operación de paridad se desparrama a varios discos, multiplicas la probabilidad de encontrar un seek lento. Los espejos también desparraman escrituras (dos discos), pero la paridad puede involucrar más discos además de lecturas, y típicamente tiene peor “latencia de cola” bajo carga.
Por qué esto sigue importando con SSDs
Los SSDs reducen el dolor de seek, pero no lo eliminan. Los SSDs tienen gestión interna de bloques de borrado, garbage collection y amplificación de escritura. Las escrituras amplificadas y dispersas de la paridad pueden aumentar la amplificación de escritura a nivel de dispositivo y causar latencias largas en la cola durante GC. Los espejos escriben más bytes que un solo disco, pero la paridad puede crear escrituras más fragmentadas y pequeñas en más dispositivos.
Encolamiento: el asesino silencioso
ZFS emite E/S por vdev. Un vdev RAIDZ se comporta como un vdev lógico respaldado por múltiples discos. Con escrituras aleatorias pequeñas, el mapeo interno del vdev y la matemática de paridad limitan cuántas operaciones independientes puede satisfacer sin contención. Los espejos, por el contrario, pueden distribuir lecturas entre discos y, con múltiples vdevs espejo, distribuir escrituras entre vdevs con menos sobrecarga de coordinación.
Chiste #1: Los arreglos de paridad son como equipos de proyecto—todos tienen que participar, así que nada termina a tiempo.
La ruta de escritura en espejo: aburrida, directa, rápida
Una escritura en espejo es simple: ZFS asigna un bloque, lo escribe en ambos lados del espejo y lo considera hecho cuando la escritura es durable según tu política de sync. No hay paridad que calcular y por lo general no hace falta leer bloques existentes para actualizar paridad.
Los espejos escalan de la forma en que la E/S aleatoria realmente escala
La unidad práctica de rendimiento en ZFS es el top-level vdev. Los IOPS de un pool son aproximadamente la suma de los IOPS de sus top-level vdevs, especialmente para cargas aleatorias. Diez vdevs espejo significan diez lugares donde ZFS puede programar escrituras independientes (cada par espejado haciendo lo suyo). Un gran vdev RAIDZ significa un dominio de programación.
Así que sí: un solo espejo de 2 discos no es mágico. Un pool de múltiples espejos sí lo es.
Diferencias de amplificación de escritura que se notan en producción
- Espejo: escribir datos nuevos dos veces (o tres veces para espejos 3-way), más actualizaciones de metadata debido a CoW.
- RAIDZ: escribir datos nuevos más paridad, a veces leer datos/paridad antiguos primero, más actualizaciones de metadata debido a CoW.
Cuando tu carga está dominada por muchas actualizaciones pequeñas, el sistema vive y muere por la sobrecarga por operación y la latencia de cola. Los espejos mantienen esa sobrecarga más baja y más predecible.
Multiplicadores específicos de ZFS: CoW, txg, metadata y sync
Aun con espejos, ZFS puede ponerse quisquilloso con escrituras aleatorias pequeñas si ignoras las partes de ZFS. Esto es lo que importa cuando diagnosticas debates “espejos vs paridad” en el mundo real.
Copy-on-write: el bloque que sobrescribiste no es el bloque que escribiste
ZFS escribe bloques nuevos y luego actualiza punteros. Eso significa que las cargas de sobrescritura aleatoria generan escrituras adicionales de metadata. En vdevs de paridad, esas escrituras de metadata están tan sujetas a penalizaciones de parcial-stripe como las escrituras de datos.
Grupos de transacción (txg) y ráfagas
ZFS agrupa escrituras en transaction groups y las vacía periódicamente. Bajo cargas intensas de escrituras pequeñas, puedes ver un patrón: el sistema buffers, luego vacía, y durante el flush aparecen picos de latencia. Los espejos tienden a vaciar más suavemente; la paridad puede volverse “punteada” si el flush se convierte en una tormenta de operaciones RMW.
Escrituras sync: donde la latencia se convierte en política
Si tu carga emite escrituras sync (las bases de datos lo hacen a menudo, los hosts de VM también), ZFS debe comprometer datos en almacenamiento estable antes de reconocer la escritura—a menos que relajes explícitamente eso con sync=disabled (no lo hagas, a menos que disfrutes explicar pérdida de datos).
En HDDs, las escrituras sync son brutales sin un SLOG adecuado porque cada operación sync fuerza un commit limitado por latencia. La paridad no ayuda. Los espejos tampoco ayudan mágicamente, pero reducen la sobrecarga extra de la paridad para que tu SLOG tenga una posibilidad real de ser el cuello de botella en lugar del arreglo.
Metadatos y la palanca del vdev “special”
La I/O de metadata puede dominar cargas de archivos pequeños y actualizaciones aleatorias. Un vdev special (típicamente SSDs espejados) puede colocar metadata (y opcionalmente bloques pequeños) en medio más rápido, reduciendo drásticamente la latencia. Esto ayuda tanto a espejos como a RAIDZ—pero a menudo salva a pools RAIDZ de sentirse inutilizables bajo churn de metadata.
Recordsize, volblocksize y tamaño real de I/O
El recordsize de ZFS (para filesystems) y el volblocksize (para zvols) influyen en cómo ZFS fragmenta los datos. Si tu BD escribe páginas de 8K en un dataset con recordsize de 128K, ZFS podría hacer más trabajo del necesario, especialmente bajo fragmentación. Pero no reduzcas ciegamente el recordsize en todas partes; eso puede aumentar la metadata y reducir la eficiencia secuencial. Ajústalo donde la carga lo justifique.
Hechos interesantes y contexto histórico (lo que la gente olvida)
- Hecho 1: ZFS surgió en Sun Microsystems a mediados de los 2000 con checksums end-to-end y CoW como ideas primarias—años antes de que “integridad de datos” fuera un checkbox de marketing.
- Hecho 2: RAIDZ fue diseñado para evitar el clásico “write hole” de RAID-5 integrando la paridad con el modelo transaccional del sistema de archivos.
- Hecho 3: La guía “el top-level vdev es la unidad de rendimiento” precede a NVMe moderno; originalmente importaba aún más en HDDs donde la latencia de seek dominaba.
- Hecho 4: Las primeras implementaciones de ZFS se construyeron a menudo para cargas de streaming (directorios de usuarios, medios, backups). Las bases de datos y la virtualización llegaron luego, y ahí el dolor de la paridad se hizo mainstream.
- Hecho 5: El ZIL existe incluso sin un SLOG dedicado. Sin SLOG, el ZIL vive en los discos del pool y las escrituras sync compiten con todo lo demás.
- Hecho 6: OpenZFS moderno ha introducido funciones como special vdevs y L2ARC persistente para atacar exactamente el problema de “metadata y I/O aleatorio pequeño” que los arreglos de paridad sufren.
- Hecho 7: Discos 4K-native y SSDs hicieron de la selección de
ashiftuna trampa permanente; un ashift erróneo puede perjudicar silenciosamente el rendimiento de I/O pequeño durante la vida del vdev. - Hecho 8: Expandir RAIDZ (añadir disco a un RAIDZ existente) históricamente ha sido difícil; operativamente, los espejos se favorecieron porque eran más fáciles de crecer sin migración.
- Hecho 9: “La paridad es más lenta” no es una ley. Para escrituras secuenciales grandes, RAIDZ puede ser muy competitivo—a veces más rápido que espejos por mejor ancho de banda utilizable por disco.
Guía rápida de diagnóstico
Este es el orden en que verifico las cosas cuando alguien dice “las escrituras pequeñas en ZFS son lentas”. El objetivo es encontrar el cuello de botella en minutos, no ganar un argumento teórico.
Primero: confirma que la carga realmente sean escrituras aleatorias pequeñas
- ¿Hablamos de un WAL de BD + páginas de datos? ¿Escrituras aleatorias de VM? ¿Tormenta de metadata?
- ¿La queja es latencia (p95/p99), o throughput, o ambas?
Segundo: identifica el tipo de vdev y cuántos top-level vdevs tienes
- Un gran vdev RAIDZ es un animal muy distinto a ocho espejos.
- Para I/O aleatorio, “más vdevs” suele vencer a “vdevs más anchos”.
Tercero: revisa el comportamiento de sync y la salud del SLOG
- Si las escrituras sync están activas y no hay un SLOG rápido (o está mal configurado), estás limitado por commits a almacenamiento estable.
- Si el SLOG existe pero está saturado, se vuelve tu punto de estrangulamiento.
Cuarto: comprueba la ocupación del pool y la fragmentación
- Pools por encima del ~80% llenos y muy fragmentados pueden convertir pequeñas escrituras en miseria para el asignador.
- Los pools de paridad sufren más porque necesitan mejor alineación para mantenerse eficientes.
Quinto: revisa ashift, recordsize/volblocksize y vdevs special
- Ashift incorrecto: impuesto permanente.
- Tamaños de bloque desajustados: impuesto evitable.
- Metadata en discos lentos: impuesto autoinfligido.
Sexto: valida que el cuello de botella sea almacenamiento, no CPU o memoria
- La paridad puede consumir CPU en checksumming/compression/parity math bajo IOPS intensivos.
- La presión del ARC puede causar lecturas adicionales y misses de metadata.
Tareas prácticas: comandos, salidas y qué decisiones tomar
Estos son chequeos reales que puedes ejecutar en un sistema Linux/OpenZFS típico. Cada tarea incluye: comando, salida de ejemplo, qué significa y la decisión que tomas a partir de ello.
Task 1: Identify vdev layout and count top-level vdevs
cr0x@server:~$ sudo zpool status -v 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
sde ONLINE 0 0 0
sdf ONLINE 0 0 0
logs
nvme0n1p2 ONLINE 0 0 0
special
mirror-1 ONLINE 0 0 0
nvme1n1p1 ONLINE 0 0 0
nvme2n1p1 ONLINE 0 0 0
errors: No known data errors
Qué significa: Tienes un vdev RAIDZ2 de nivel superior (un “carril” de rendimiento para escrituras aleatorias), más un SLOG y un vdev special en espejo.
Decisión: Si la latencia de escrituras aleatorias es el problema, o (a) aceptas los límites de la paridad y mitigas con special/SLOG/ajustes, o (b) rediseñas hacia múltiples vdevs espejo.
Task 2: Watch per-vdev IOPS and latency during the complaint
cr0x@server:~$ sudo zpool iostat -v tank 1 5
capacity operations bandwidth
pool alloc free read write read write
-------------------------- ----- ----- ----- ----- ----- -----
tank 6.20T 1.80T 120 980 3.2M 12.4M
raidz2-0 6.20T 1.80T 120 980 3.2M 12.4M
sda - - 20 165 520K 2.1M
sdb - - 18 170 510K 2.0M
sdc - - 22 160 540K 2.1M
sdd - - 21 162 530K 2.0M
sde - - 19 163 510K 2.1M
sdf - - 20 160 520K 2.1M
-------------------------- ----- ----- ----- ----- ----- -----
Qué significa: Las escrituras están distribuidas, pero el pool está haciendo ~980 operaciones de escritura/s en total. En HDDs, eso ya coquetea con el límite por seek.
Decisión: Si la aplicación necesita más IOPS o menor p99, RAIDZ en HDDs probablemente no sea la herramienta correcta. Considera espejos, SSDs o mover el conjunto caliente a special/SSD.
Task 3: Check sync settings at the dataset/zvol level
cr0x@server:~$ sudo zfs get -r sync tank
NAME PROPERTY VALUE SOURCE
tank sync standard default
tank/db sync standard inherited from tank
tank/vmstore sync standard inherited from tank
Qué significa: Las escrituras sync se honran normalmente.
Decisión: Bien. Ahora asegúrate de tener un SLOG rápido y con protección ante pérdida de energía si la latencia de sync es alta. No “arregles” esto deshabilitando sync salvo que los datos sean prescindibles.
Task 4: Confirm SLOG presence and whether it’s actually being used
cr0x@server:~$ sudo zpool iostat -v tank 1 3
capacity operations bandwidth
pool alloc free read write read write
-------------------------- ----- ----- ----- ----- ----- -----
tank 6.20T 1.80T 200 1100 4.1M 13.0M
raidz2-0 6.20T 1.80T 150 700 3.5M 10.8M
nvme0n1p2 - - 50 400 620K 2.2M
-------------------------- ----- ----- ----- ----- ----- -----
Qué significa: El dispositivo de log muestra actividad de escritura. El tráfico sync está llegando allí.
Decisión: Si el dispositivo log es lento o está saturado, actualízalo. Si es rápido y aún hay mala latencia, el problema es el pool (a menudo paridad + fragmentación).
Task 5: Check pool fullness (allocation pressure gets ugly)
cr0x@server:~$ zfs list -o name,used,avail,refer,mountpoint tank
NAME USED AVAIL REFER MOUNTPOINT
tank 6.20T 1.80T 96K /tank
Qué significa: Aproximadamente 77% usado. Aún no es terrible.
Decisión: Si estás por encima de ~80–85% usado y ves pain en escrituras aleatorias, planifica alivio de capacidad. Los espejos toleran mejor el “llenado” que RAIDZ, pero ningún pool gusta de la inanición del asignador.
Task 6: Check fragmentation (a proxy for allocator pain)
cr0x@server:~$ sudo zpool list -o name,size,alloc,free,frag,health tank
NAME SIZE ALLOC FREE FRAG HEALTH
tank 8T 6.2T 1.8T 41% ONLINE
Qué significa: 41% de fragmentación no es un crimen, pero bajo sobrescrituras aleatorias puede producir latencia real.
Decisión: Si frag está alto y la carga es de escrituras aleatorias, planifica migrar/replicar a un pool nuevo o añade vdevs (los espejos ayudan más) para darle espacio al asignador.
Task 7: Verify ashift (misalignment tax is forever)
cr0x@server:~$ sudo zdb -C tank | egrep -i "ashift|vdev_tree" -n | head
45: vdev_tree:
78: ashift: 12
Qué significa: ashift=12 (sectores de 4K). Usualmente correcto para discos modernos.
Decisión: Si ves ashift=9 en discos 4K, has construido un problema de rendimiento que no puedes afinar. La solución es rebuild/migración.
Task 8: Check recordsize (filesystems) and volblocksize (zvols)
cr0x@server:~$ sudo zfs get recordsize,compression tank/db
NAME PROPERTY VALUE SOURCE
tank/db recordsize 128K local
tank/db compression lz4 local
Qué significa: El dataset de BD usa recordsize de 128K. Si la BD hace escrituras de páginas de 8K, esto podría ser demasiado grande según los patrones de acceso.
Decisión: Considera ajustar recordsize para que coincida con el tamaño de página de la BD solo cuando el dataset contenga realmente archivos de datos de BD y la carga sea de sobrescritura aleatoria intensa. Mide antes/después.
Task 9: Check whether compression is helping or hurting CPU
cr0x@server:~$ sudo zfs get compression,compressratio tank/db
NAME PROPERTY VALUE SOURCE
tank/db compression lz4 local
tank/db compressratio 1.68x -
Qué significa: La compresión es efectiva; menos bytes van al disco, lo que suele ayudar al rendimiento de escrituras aleatorias.
Decisión: Mantén lz4 salvo que la CPU esté saturada. La compresión frecuentemente ayuda a pools de paridad más que a espejos porque reduce la carga de paridad por escritura lógica.
Task 10: Check whether sync latency is dominated by the log device
cr0x@server:~$ sudo iostat -x 1 3
Linux 6.8.0 (server) 12/26/2025 _x86_64_ (16 CPU)
Device r/s w/s r_await w_await aqu-sz %util
sda 20.0 165.0 12.1 35.8 3.2 92.0
sdb 18.0 170.0 11.7 36.5 3.3 93.5
sdc 22.0 160.0 12.8 34.2 3.1 90.1
nvme0n1 50.0 400.0 0.3 0.8 0.2 18.0
Qué significa: Los HDDs muestran write await alto (~35 ms) y alta utilización; el dispositivo NVMe de log está bien. El cuello de botella es el pool, no el SLOG.
Decisión: Aquí es donde la paridad en HDDs normalmente pierde con escrituras aleatorias pequeñas. Mueve la carga a espejos/SSD, añade vdevs espejo o separa las cargas en pools.
Task 11: Check ARC pressure (memory misses can trigger extra I/O)
cr0x@server:~$ cat /proc/spl/kstat/zfs/arcstats | egrep "c_max|c |size|hits|misses" | head
c 4 51539607552
c_max 4 51539607552
size 4 49821667328
hits 4 182736455
misses 4 32736455
Qué significa: ARC está cerca del máximo y tiene misses. Los misses no son automáticamente malos; la proporción importa y la carga importa.
Decisión: Si ARC está thrashing (spikes en la tasa de misses) y estás haciendo lectura-modificación-escritura en stripes de paridad, la latencia puede empeorar. Añade RAM, ajusta cargas o aísla datasets.
Task 12: Look for slow I/O and vdev-level timeouts in kernel logs
cr0x@server:~$ sudo dmesg | egrep -i "zfs|I/O error|blk_update_request|timeout" | tail -n 8
[12345.678901] zfs: vdev disk sdb: slow I/O, 35 sec, zio 0x0000000abcd123
[12347.112233] blk_update_request: I/O error, dev sdb, sector 123456789
[12347.445566] zfs: vdev disk sdb: repairing
Qué significa: Un disco está lento o reporta errores. Los vdevs de paridad son particularmente sensibles a un nodo rezagado; la latencia de cola del vdev entero empeora.
Decisión: Reemplaza o remedia el disco lento. No ajustes rendimiento alrededor de hardware que falla.
Task 13: Check ZFS write throttle behavior (txg pressure)
cr0x@server:~$ cat /proc/spl/kstat/zfs/dmu_tx | head -n 15
dmu_tx statistics:
dmu_tx_assigned: 9876543
dmu_tx_delay: 12345
dmu_tx_error: 0
Qué significa: dmu_tx_delay incrementa cuando ZFS debe throttlear escritores porque el pool no puede vaciar lo suficientemente rápido.
Decisión: Si el delay sube durante el incidente, estás limitado por escritura en almacenamiento. Los espejos con más vdevs pueden reducir esto; también medios más rápidos o dar forma a la carga.
Task 14: Validate special vdev is actually taking metadata/small blocks
cr0x@server:~$ sudo zpool list -v tank
NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
tank 8T 6.2T 1.8T - - 41% 77% 1.00x ONLINE -
raidz2-0 8T 6.2T 1.8T - - 41% 77%
special 1T 220G 804G - - 9% 21%
Qué significa: El vdev special tiene asignación, lo que sugiere que metadata (y quizá bloques pequeños) están aterrizando allí.
Decisión: Si special muestra asignación cercana a cero a pesar de estar configurado, revisa propiedades del dataset como special_small_blocks. Si la metadata está en HDDs, estás pagando latencia evitable.
Task 15: Confirm dataset properties that often sabotage small-write latency
cr0x@server:~$ sudo zfs get atime,logbias,primarycache,secondarycache tank/db
NAME PROPERTY VALUE SOURCE
tank/db atime on default
tank/db logbias latency default
tank/db primarycache all default
tank/db secondarycache all default
Qué significa: atime está on (escrituras metadata extra), logbias es latency (bueno para cargas sync-heavy), caching está por defecto.
Decisión: Para datasets de BD/VM, pon atime=off salvo que realmente lo necesites. Considera logbias=throughput solo si entiendes tu perfil de sync y tienes una razón sólida.
Task 16: Determine whether you’re using zvols for VMs and what block size they use
cr0x@server:~$ sudo zfs list -t volume -o name,volsize,volblocksize,used tank/vmstore
NAME VOLSIZE VOLBLOCKSIZE USED
tank/vmstore 2T 8K 1.4T
Qué significa: El zvol usa bloques de 8K, lo cual puede ser razonable para cargas de VM que hacen escrituras aleatorias pequeñas.
Decisión: Si el volblocksize es enorme (p. ej., 128K) para imágenes de VM de escritura aleatoria, estás creando amplificación de escritura. Cambiar volblocksize requiere recrear el zvol—planifica la migración.
Tres mini-historias corporativas desde las trincheras del almacenamiento
Mini-historia 1: El incidente causado por una suposición equivocada
La empresa: SaaS mediana, un clúster PostgreSQL primario, algunos réplicas de lectura y un sistema de jobs en background que amaba las pequeñas actualizaciones. Tenían un nodo de almacenamiento nuevo con un gran pool RAIDZ2 porque “necesitamos capacidad y redundancia”. En la hoja de cálculo de dimensionamiento, el throughput secuencial parecía fantástico.
La suposición: “IOPS es IOPS, y RAIDZ2 tiene más discos, así que será más rápido.” Nadie lo dijo tan crudo en voz alta, pero vivía en la arquitectura.
El día del lanzamiento fue bien. Luego el dataset empezó a fragmentarse y el conjunto de trabajo creció. El síntoma no fue “bajo throughput”. El síntoma fueron paradas periódicas de 1–3 segundos en commits. Hilos de la aplicación se apilaron, el p99 se disparó y el on-call recibió el patrón familiar de alertas: CPU baja, red baja, disco ocupado, pero no moviendo mucho dato.
El postmortem reveló la verdadera causa: escrituras sync pequeñas aterrizando en un vdev de paridad con HDDs. El ZIL estaba en-pool (sin SLOG), así que cada fsync arrastraba los discos a la pelea. Bajo concurrencia, el arreglo se volvió generador de latencia de cola.
La solución no fue ingeniosa. Migraron la BD primaria a un pool construido con múltiples vdevs espejo en SSDs, mantuvieron RAIDZ2 para backups y datos masivos, y de repente el mismo código de aplicación parecía “optimizado”. La arquitectura de almacenamiento había sido el bug.
Mini-historia 2: La optimización que salió mal
En otro lugar, problema distinto: clúster de virtualización con decenas de cargas mixtas. Alguien notó que el pool de paridad “desperdiciaba” potencial y propuso un tweak simple de rendimiento: deshabilitar sync en el dataset de VM y aumentar recordsize para reducir sobrecarga. Se vendió como “seguro porque el hypervisor ya cachea escrituras”.
Durante una semana fue genial. Los gráficos de latencia se suavizaron. La gente felicitó el cambio. Luego un host sufrió un crash por energía y un reboot desordenado. Varias VMs volvieron con errores de sistema de archivos. Una tenía una base de datos que no arrancaba limpiamente.
La lección incómoda: sync=disabled no significa “ligeramente menos seguro”. Significa “ZFS puede mentir sobre durabilidad”. Para imágenes de VM y bases de datos, esa mentira eventualmente se convierte en incidente, y siempre ocurre en el peor momento.
Revirtieron el dataset a sync=standard, añadieron dispositivos SLOG espejados con protección ante pérdida de energía y afinando el sistema de la manera aburrida: volblocksize correcto para zvols, usar espejos para niveles sensibles a latencia y mantener paridad para niveles de capacidad.
Chiste #2: Deshabilitar sync es como quitar detectores de humo porque pitan demasiado—casa silenciosa, futuro emocionante.
Mini-historia 3: La práctica aburrida pero correcta que salvó el día
Una organización financiera con requisitos de auditoría ejecutaba ZFS para una mezcla de servicios de archivos y un sistema transaccional. No eran sofisticados. Eran disciplinados. Cada cambio de pool requería una línea base de rendimiento y un plan de rollback. Cada trimestre probaban restore y failover. Cada mes revisaban salud del pool y resultados de scrub.
Una mañana, un pool de paridad empezó a mostrar mayor latencia de escritura y errores de checksum ocasionales. Nada estaba “caído”, pero el equipo lo trató como indicador temprano, no ruido de fondo. Revisaron zpool status, vieron un disco con errores en aumento y lo reemplazaron en una ventana controlada.
Durante el resilver, limitaron jobs por lotes y movieron la carga transaccional a un pool basado en espejos que mantenían específicamente para datasets “calientes”. Como fue planeado, no improvisado, el negocio casi no lo notó.
Después del incidente, el postmortem fue del gusto de los SRE: corto, calmado y mayormente sobre lo que funcionó. La práctica aburrida—scrubs, líneas base y segregación de cargas—fue la razón por la que no se convirtió en titular.
Errores comunes: síntomas → causa raíz → solución
Mistake 1: “RAIDZ tiene más discos, así que debe tener más IOPS”
Síntomas: p99 spikes de latencia de escritura, MB/s bajos pero %util alto en discos, commits de BD parados.
Causa raíz: Un vdev RAIDZ es un carril de I/O aleatorio; las escrituras pequeñas disparan sobrecarga de partial-stripe y patrones RMW.
Solución: Usa múltiples vdevs espejo para tiers de escritura aleatoria. Mantén RAIDZ para capacidad/streaming. Si estás atrapado con RAIDZ, añade special vdev y asegura sync/SLOG correcto.
Mistake 2: Pool demasiado lleno para que la geometría de paridad se mantenga feliz
Síntomas: Degradación gradual del rendimiento, CPU del asignador elevada, aumentos en dmu_tx_delay, fragmentación creciente.
Causa raíz: Alta utilización del pool reduce opciones de asignación; la fragmentación aumenta y las escrituras parcial-stripe se vuelven más comunes.
Solución: Mantén pools por debajo del ~80% cuando las escrituras aleatorias importen. Añade capacidad (preferiblemente nuevos vdevs) o migra a un pool fresco.
Mistake 3: Sin SLOG para cargas sync-heavy (o usar un SSD barato como SLOG)
Síntomas: Latencia fsync espantosa, WAL de BD detenido, invitados VM reportan latencia en flush de disco.
Causa raíz: Las escrituras ZIL aterrizan en el pool principal o en un dispositivo de log con mala latencia o sin protección ante pérdida de energía.
Solución: Añade SLOG espejado, con protección ante pérdida de energía, para cargas sync. Valida con zpool iostat -v y métricas de latencia del dispositivo.
Mistake 4: Ashift equivocado
Síntomas: Rendimiento crónico pobre en I/O pequeño independientemente de ajustes; amplificación de escritura parece “misteriosa”.
Causa raíz: ashift demasiado pequeño causa lectura-modificación-escritura a nivel de dispositivo por desalineación de sectores.
Solución: Reconstruir/migrar vdevs con ashift correcto. No hay sysctl mágico para deshacerlo.
Mistake 5: Tratar recordsize como un “knob” universal de rendimiento
Síntomas: Algunas cargas mejoran, otras empeoran; metadata se dispara; backups se enlentecen.
Causa raíz: recordsize afecta el layout en disco y el comportamiento de metadata; reducirlo en todas partes aumenta la sobrecarga.
Solución: Ajusta por dataset. Archivos de BD podrían querer 8K/16K; archivos multimedia 1M. Mide con carga realista.
Mistake 6: Ignorar metadata como una carga de primer orden
Síntomas: “Pero solo estamos escribiendo poco” mientras la latencia es horrible; operaciones de directorio lentas; snapshots de VM lentos.
Causa raíz: La I/O de metadata domina; los vdevs de paridad manejan mal las actualizaciones de metadata bajo churn.
Solución: Usa special vdev (SSD espejo) para metadata y posiblemente bloques pequeños; mantenlo redundante; móntoréalo como si fuera datos de producción (porque lo es).
Listas de verificación / plan paso a paso
Design checklist: choosing mirrors vs parity for a new pool
- Si son bases de datos, almacenamiento de VM o cualquier cosa sync-heavy: por defecto usa múltiples vdevs espejo.
- Si son backups, medios, logs, dumps analíticos: RAIDZ está bien; optimiza para capacidad.
- Cuenta vdevs, no discos: los IOPS aleatorios escalan con los top-level vdevs. Planea suficientes vdevs espejo para alcanzar IOPS objetivo con margen.
- Planifica la historia de sync: acepta latencia en discos del pool, o añade un SLOG espejado adecuado. No lo “resuelvas” con
sync=disabled. - Planifica metadata: si la carga es heavy en metadata, considera un vdev special espejo.
- Mantén margen de espacio: diseña para permanecer por debajo del ~80% si las escrituras aleatorias importan.
- Elige ashift deliberadamente: asume sectores de 4K como mínimo; favorece ashift=12 salvo que tengas una razón.
- Decide tamaño de bloque por dataset: ajusta recordsize/volblocksize para coincidir con la carga, no con tu intuición.
Migration plan: moving from RAIDZ to mirrors without drama
- Construye el pool nuevo (espejos, ashift correcto, tiering en SSD si se necesita) junto al pool viejo.
- Configura propiedades del dataset en el pool nuevo antes de copiar (recordsize, compression, atime, sync, special_small_blocks).
- Usa ZFS send/receive para filesystems; para zvols, planifica downtime de invitados o una estrategia de replicación consistente con tu hypervisor.
- Ejecuta una línea base de rendimiento en paralelo antes del cutover: mide latencia de fsync, IOPS de escritura aleatoria, p99.
- Cambio con rollback: mantén el pool viejo en solo-lectura por una ventana; monitorea errores y rendimiento.
- Después del cutover vuelve a comprobar fragmentación y tasa de llenado; arregla el siguiente cuello de botella (a menudo red o CPU).
Operational checklist: keeping small random write performance from decaying
- Monitorea utilización de pool y tendencias de fragmentación mensualmente.
- Haz scrubs según calendario; trata errores de checksum como urgentes, no como cosméticos.
- Monitorea latencia, no solo throughput. El p99 de escritura es la verdad.
- Mantén firmware consistente en SSDs usados para SLOG/special vdev.
- Haz pruebas de carga controladas tras cambios mayores (kernel, versión de ZFS, reemplazo de discos).
Preguntas frecuentes
1) ¿RAIDZ siempre es más lento que los espejos?
No. Para lecturas/escrituras secuenciales grandes y tiers enfocados en capacidad, RAIDZ puede ser excelente. El dolor es específicamente escrituras aleatorias pequeñas y cargas sync-heavy.
2) ¿Por qué los espejos “escalan” mejor para I/O aleatorio?
Porque cada top-level vdev es una unidad de programación. Muchos vdevs espejo dan a ZFS más carriles independientes para operaciones aleatorias. Un solo vdev RAIDZ ancho sigue siendo un solo carril.
3) Si añado más discos a RAIDZ, ¿obtengo más IOPS?
Obtienes más ancho de banda agregado y a veces mejor concurrencia, pero también haces stripes más anchas y aumentas la coordinación de paridad. Para escrituras aleatorias pequeñas, rara vez escala como necesitas.
4) ¿Un SLOG rápido puede hacer que RAIDZ sea bueno en escrituras aleatorias pequeñas?
Un SLOG ayuda la latencia de escrituras sync acelerando commits ZIL. No elimina la sobrecarga de paridad para las escrituras de datos que aún aterrizan en el pool durante los flush de txg.
5) ¿Debería poner sync=disabled por rendimiento?
Sólo para datos que puedas perder sin remordimiento. Para bases de datos e imágenes de VM, es una falla esperando a ocurrir por un evento de energía. Usa SLOG adecuado y espejos en su lugar.
6) ¿Un vdev special reemplaza la necesidad de espejos?
No. Un vdev special puede mejorar drásticamente metadata y rendimiento de bloques pequeños, lo que puede hacer que RAIDZ se sienta menos horrible bajo ciertas cargas. No cambia la matemática de paridad para bloques de datos normales.
7) ¿RAIDZ en SSD es aceptable para escrituras aleatorias pequeñas?
Mejor que RAIDZ en HDD, pero aun así suele quedar detrás de múltiples espejos en latencia de cola, especialmente bajo cargas sync-heavy o fragmentadas. Los SSDs enmascaran el problema; no lo eliminan.
8) ¿Qué ajustes de dataset suelen mejorar el comportamiento de escrituras aleatorias pequeñas?
compression=lz4, atime=off para datasets de BD/VM, recordsize/volblocksize apropiados para la carga y una estrategia de sync/SLOG correcta. Un vdev special para cargas heavy en metadata es una palanca importante.
9) ¿Cuál es la forma más fiable de probar que la paridad es el cuello de botella?
Correlaciona picos de latencia de la aplicación con zpool iostat -v, await/%util de iostat -x y los indicadores de throttling de ZFS como dmu_tx_delay. Si los discos del pool están ocupados con alta await y bajo MB/s, estás limitado por IOPS/latencia.
10) ¿Cuál es la frase que quieres que los SRE recuerden aquí?
“La esperanza no es una estrategia.”
— General Gordon R. Sullivan
Próximos pasos que puedes ejecutar esta semana
- Clasifica tus datasets: cuáles son sensibles a latencia (BD/VM), cuáles son para throughput/capacidad (backups, archivos).
- Ejecuta la guía de diagnóstico rápido durante una ventana de incidente real: captura
zpool iostat -veiostat -xcuando los usuarios se quejen. - Corrige las victorias baratas:
atime=offdonde corresponda, verificacompression=lz4, confirma política de sync y salud del SLOG. - Si estás en paridad para cargas hot: decide si vas a (a) añadir un “pool hot” basado en mirrors, (b) añadir vdev special y tiering en SSD, o (c) rediseñar a espejos por completo.
- Planifica la migración como un cambio operativo, no como un hobby de fin de semana: baseline, replicar, cutover con rollback.
Si tu carga son escrituras aleatorias pequeñas y a tu negocio le importa la latencia, los espejos no son un lujo. Son la opción sensata por defecto. La paridad es para cuando la eficiencia de capacidad importa más que la latencia de cola. Elige deliberadamente y ZFS hará lo que hace mejor: mantener tus datos correctos mientras duermes.