Tu base de datos estaba “bien” en staging. Luego llegó producción con usuarios reales, latencias reales y el tipo de amplificación de escritura que convierte
benchmarks confiados en fanfiction de rendimiento.
Si ejecutas ZFS bajo una base de datos, las escrituras síncronas son donde vive la verdad. También son donde la mayoría de las pruebas con fio hacen trampa por accidente: flags incorrectos, suposiciones equivocadas, ruta de I/O errónea, garantías de durabilidad distintas. Esta es una guía de campo para ejecutar fio contra ZFS como si realmente te importara que los datos sobrevivan a un corte de energía.
Qué significan realmente las “escrituras síncronas” en ZFS (y por qué a las bases de datos les importa)
Las bases de datos no quieren “escrituras rápidas”. Quieren escrituras confirmadas. Cuando una base de datos dice “transacción confirmada”, hace una promesa:
si la máquina pierde energía inmediatamente después, los datos confirmados siguen ahí cuando vuelve.
Esa promesa se implementa con un conjunto pequeño de comportamientos:
fsync(), fdatasync(), O_DSYNC, O_SYNC, y a veces semánticas FUA
en la pila de almacenamiento. La mayor parte de la durabilidad de una base de datos se reduce a un requisito estricto: el registro de log está en almacenamiento estable antes de que el “OK” regrese al cliente.
ZFS complica esto de manera positiva (modelo de consistencia fuerte) y de forma confusa (copy-on-write + transaction groups + intent log).
ZFS puede aceptar escrituras en memoria y confirmarlas más tarde en un TXG (transaction group). Eso es I/O en buffer normal. Pero cuando una aplicación
pide semánticas síncronas, ZFS debe asegurarse de que la escritura sea durable antes de devolver el control.
Entra el ZIL (ZFS Intent Log). El ZIL no es una “caché de escritura” en el sentido habitual; es un mecanismo para satisfacer solicitudes síncronas rápidamente
sin forzar un commit completo del pool de inmediato. Si el sistema se cae, los registros del ZIL se reproducen al importar para llevar el sistema de archivos a un
estado consistente que incluya esas operaciones síncronas.
Normalmente el ZIL vive en el propio pool. Un SLOG (dispositivo de log separado) es un dispositivo opcional dedicado que contiene los registros del ZIL. Bien hecho,
un SLOG convierte la “latencia de escritura síncrona” en “latencia del dispositivo SLOG”. Mal hecho, convierte “base de datos durable” en “espero que el UPS esté teniendo un buen día.”
Tu trabajo al probar es medir:
latencia e IOPS bajo semánticas síncronas que coincidan con la base de datos,
y hacerlo asegurándote de que no estés midiendo por accidente RAM, el page cache o una configuración de durabilidad que realmente no usas en prod.
Hechos y contexto: cómo llegamos hasta aquí
- Hecho 1: ZFS nació en Sun Microsystems a mediados de los 2000 con foco en integridad de datos de extremo a extremo (checksums en todas partes), no en “números pico de fio”.
- Hecho 2: Los sistemas de archivos tradicionales a menudo dependían de caching write-back y un journal; ZFS usa copy-on-write más commits transaccionales (TXGs), lo que cambia cómo se comporta la “latencia de escritura”.
- Hecho 3: El ZIL existe específicamente para hacer que las operaciones síncronas sean rápidas sin forzar un sync de TXG completo en cada petición tipo fsync.
- Hecho 4: El almacenamiento empresarial temprano comúnmente incluía caché de escritura con batería; hoy, las unidades NVMe con protección contra pérdida de energía (PLP) son el análogo de consumo más cercano para logging síncrono seguro y de baja latencia.
- Hecho 5: Históricamente las bases de datos se optimizaron para discos giratorios, donde el logging secuencial era clave; en SSD/NVMe, la variación de latencia (tail latency) se vuelve el verdadero asesino.
- Hecho 6: Las APIs de I/O asíncrono en Linux evolucionaron por separado (libaio, io_uring), y fio puede ejercer muchos caminos de código—algunos de los cuales no mapean a cómo tu base de datos escribe su WAL/redo log.
- Hecho 7: “Write cache enabled” en un disco ha sido una trampa desde los inicios de SATA; puede mejorar benchmarks mientras destruye silenciosamente garantías de durabilidad ante pérdida de energía.
- Hecho 8: Propiedades de dataset de ZFS como
sync,recordsize,logbiasyprimarycachepueden cambiar el rendimiento materialmente sin tocar el código de la aplicación—gran poder, gran capacidad para dispararte en el pie.
Modelo mental de ZFS para gente de bases de datos: TXGs, ZIL, SLOG y mentiras
TXGs: el metrónomo detrás de tu carga de escritura
ZFS agrupa modificaciones en transaction groups. Cada pocos segundos (comúnmente alrededor de 5s, configurable), un TXG se sincroniza a disco. Si tu carga
es mayormente escrituras en buffer asíncronas, el rendimiento que ves está fuertemente ligado al comportamiento de los TXG: los datos sucios se acumulan y luego se vacían. Eso
puede producir patrones “dientes de sierra” de latencia—flushes por ráfaga, luego calma.
Para escrituras síncronas, ZFS no puede simplemente esperar al próximo sync de TXG. Necesita reconocer solo después de que la escritura sea durable. Por eso existe el ZIL.
ZIL: no es un segundo journal, no es un log de base de datos, no es magia
El ZIL registra la intención de las operaciones síncronas. Se escribe de forma secuencial-ish, en registros pequeños. Cuando el TXG eventualmente confirma los
bloques de datos reales, los registros correspondientes del ZIL dejan de ser necesarios y pueden descartarse.
Consecuencia importante: en estado estable, el ZIL es sobre latencia, no sobre capacidad. Un dispositivo SLOG no necesita ser enorme.
Necesita ser baja latencia, durable y consistente bajo presión.
SLOG: el dispositivo de “no hagas terribles las escrituras síncronas”
Con un SLOG, ZFS escribe los registros del ZIL en ese dispositivo en vez de en el pool principal. Si el SLOG es rápido y tiene protección contra pérdida de energía,
la latencia de escrituras síncronas puede caer dramáticamente.
Si el SLOG es rápido pero no seguro frente a pérdida de energía, has construido un aparato para corromper bases de datos. Sí, puedes tener suerte. La suerte no es diseño.
Dónde la gente se engaña a sí misma
La mayoría de los “benchmarks ZFS para bases de datos” fallan en una de tres maneras:
- No están realmente haciendo I/O síncrono. fio escribe datos en buffer y reporta números gloriosos. La base de datos no se comporta así.
- Hacen I/O síncrono, pero ZFS no lo está respetando como en producción. Dataset
sync=disabledo capas de virtualización cambian las semánticas. - Miden throughput e ignoran la distribución de latencia. Las bases de datos mueren por p99 y p999, no por MB/s promedio.
“Todo es rápido en promedio” es la forma de recibir un llamado a las 02:13. La base de datos no está enfadada por el promedio. Está enfadada por los outliers.
Una cita que vale la pena pegar en tu monitor:
“La esperanza no es una estrategia.”
—General Gordon R. Sullivan
Broma #1: Si tu benchmark termina en 30 segundos y tus conclusiones duran tres años, haces ingeniería de rendimiento como un horóscopo.
Reglas de fio: cómo los benchmarks hacen trampa por accidente
Regla 1: “sync” debe significar lo mismo para fio, el SO y ZFS
fio puede comportarse en modo síncrono de varias maneras:
fsync=1 (llamar fsync después de cada escritura),
fdatasync=1,
sync=1 (usar O_SYNC),
dsync=1 (usar O_DSYNC),
direct=1 (O_DIRECT, evitar page cache),
y el motor de I/O puede cambiar aún más las semánticas (psync, libaio, io_uring).
Las bases de datos comúnmente hacen escrituras en buffer a su archivo WAL/redo y llaman fsync en commit (o group commit). Algunas usan O_DSYNC / O_DIRECT
según la configuración. Eso significa que tu prueba con fio debe seleccionarse según la configuración de la base de datos que ejecutas, no según lo que haga
la gráfica más bonita.
Regla 2: I/O en buffer + archivos pequeños = benchmark de page cache
Si ejecutas fio contra un archivo pequeño sin direct=1, puedes acabar midiendo el page cache de Linux. Eso todavía puede ser útil,
pero no es lo que crees.
Para pruebas de escrituras síncronas, la peor mentira es:
crees que mides latencia de durabilidad, pero mides velocidad de RAM más un flush ocasional.
Regla 3: “fsync por escritura” no siempre es lo que hace tu base de datos
Poner fsync=1 en fio fuerza un fsync después de cada llamada a write. Eso modela una base de datos sin group commit. Muchas bases de datos realizan
group commit: varias transacciones comparten un fsync. Si tu base de datos en producción agrupa commits, “fsync por escritura de 4k” puede subestimar drásticamente el throughput y sobrestimar la latencia.
La solución no es hacer trampa. La solución es modelar el group commit intencionalmente (múltiples hilos escribiendo, cadencia de fsync, o una mezcla de síncrono y asíncrono).
Regla 4: los percentiles de latencia son el producto
Cuando las escrituras síncronas son lentas, tu base de datos espera. Eso se ve como picos de latencia y acumulación de colas. Siempre captura:
p50, p95, p99, y idealmente p99.9, además de IOPS bajo una latencia objetivo.
Regla 5: verifica que tu prueba sea realmente síncrona
No confíes en un archivo de configuración porque “se ve bien.” Haz que el sistema lo demuestre: traza syscalls, observa el comportamiento del ZIL, confirma propiedades
del dataset y confirma que los dispositivos estén respetando los flushes.
Diseñar una prueba honesta con fio para escrituras síncronas de bases de datos
Partir desde la ruta de durabilidad de la base de datos
Elige una configuración de base de datos y mápala a fio:
- PostgreSQL por defecto: escrituras WAL en buffer + fsync. Analogo en fio: escrituras en buffer con
fsync=1o fsync periódico, según el agrupamiento de commits. - MySQL/InnoDB: depende de
innodb_flush_methodyinnodb_flush_log_at_trx_commit. Analogo en fio: mezcla de patrones fsync y O_DSYNC. - SQLite FULL: barreras fsync frecuentes. Analogo en fio: escrituras pequeñas síncronas con fsync o O_DSYNC.
Escoge un tamaño de bloque que coincida con el log, no con la tabla
WAL/redo normalmente se escribe en trozos (a menudo 8k, 16k, 32k, a veces 4k). Usa bs=8k o bs=16k para pruebas tipo WAL.
No uses bs=1m y lo llames “bases de datos.”
Usa múltiples jobs para modelar concurrencia y group commit
La mayoría de los sistemas confirman múltiples transacciones concurrentemente. Incluso si cada transacción es “síncrona”, el sistema puede pipelinear trabajo. Ejecuta múltiples jobs de fio
con un archivo compartido o archivos separados para modelar la contención. Pero entiende el trade-off:
más hilos pueden ocultar la latencia de una sola escritura mientras aumentan la latencia de cola.
Mantén los archivos lo suficientemente grandes para evitar localidad falsa
Para pruebas de log, puedes usar un archivo del tamaño de segmentos WAL (p. ej., unos pocos GB), pero asegúrate de que no estás simplemente reescribiendo los mismos bloques que están
calientes en el ARC y caches de metadatos. Además: no ejecutes todo contra un dataset diminuto con primarycache=all y luego te preguntes por qué es “rápido.”
Separa las pruebas “log síncrono” de las de “flush de datos”
Una base de datos tiene al menos dos personalidades de escritura:
- Escrituras de log (síncronas): pequeñas, sensibles a latencia, críticas para durabilidad.
- Escrituras de datos (algo asíncronas): más grandes, orientadas a throughput, impulsadas por checkpoints.
Si las mezclas en un solo job de fio, no podrás diagnosticar nada. Crea al menos dos perfiles de prueba.
Corre lo bastante para alcanzar estado estable y observar ciclos TXG
Si tu tiempo de ejecución es más corto que unos pocos intervalos de sync de TXG, puedes ser engañado por caches calientes, comportamiento inicial de asignación y boosts de dispositivo a corto plazo. Para latencia de escrituras síncronas, 2–5 minutos es un mínimo decente; 10–20 minutos es mejor si persigues p99.9.
Broma #2: Lo único más rápido que un SLOG NVMe es un benchmark que se olvidó de habilitar sync.
Tareas prácticas (comandos, salidas, decisiones)
Estas son las tareas que realmente ejecuto cuando alguien dice “ZFS es lento para nuestra base de datos” y me entrega un único número promedio de IOPS.
Cada tarea incluye: comando, qué significa la salida y qué decisión tomar a partir de ella.
Task 1: Confirmar la propiedad del dataset sync (el mayor “ups”)
cr0x@server:~$ zfs get -o name,property,value,source sync tank/db
NAME PROPERTY VALUE SOURCE
tank/db sync standard local
Significado: standard honra las solicitudes de sync de la aplicación. Si esto dice disabled, tu “benchmark de escrituras síncronas”
probablemente está probando el modo fantasía.
Decisión: Si en producción la durabilidad debe ser garantizada, deja standard (o always si debes forzar sync). Si ves
disabled cerca de una base de datos, trátalo como un sev-1 esperando un evento de energía.
Task 2: Comprobar si existe un SLOG y si realmente se está usando
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
nvme0n1p2 ONLINE 0 0 0
nvme1n1p2 ONLINE 0 0 0
logs
mirror-1 ONLINE 0 0 0
nvme2n1p1 ONLINE 0 0 0
nvme3n1p1 ONLINE 0 0 0
Significado: Está presente un dispositivo de log en espejo. Bien: las escrituras del ZIL van al SLOG, y el mirror reduce el riesgo de pérdida por dispositivo único.
Decisión: Si no hay sección logs y la latencia de sync es alta, evalúa un SLOG con capacidad PLP. Si hay un SLOG pero es un SSD de consumidor con seguridad de energía desconocida, asume riesgo de durabilidad hasta que se demuestre lo contrario.
Task 3: Verificar que el dispositivo SLOG reporte caché de escritura y comportamiento de flush
cr0x@server:~$ sudo hdparm -W /dev/nvme2n1
/dev/nvme2n1:
write-caching = 1 (on)
Significado: El caching de escritura está “on”. En NVMe esto es normal y no automáticamente inseguro; la pregunta real es si la unidad tiene PLP.
Decisión: Confirma PLP vía especificaciones del proveedor o nvme id-ctrl. Si no puedes establecer seguridad frente a pérdida de energía, no lo uses como SLOG para una base de datos que reclama durabilidad.
Task 4: Identificar indicadores de protección contra pérdida de energía (mejor esfuerzo desde el SO)
cr0x@server:~$ sudo nvme id-ctrl /dev/nvme2n1 | egrep -i 'oncs|vwc|frmw'
oncs : 0x001f
vwc : 0x0001
frmw : 0x0016
Significado: vwc indica presencia de caché de escritura volátil. Esto no confirma PLP; confirma que deberías preocuparte.
Los bits de oncs muestran soporte de comandos opcionales como flush. Aun así necesitas la verdad del proveedor para PLP.
Decisión: Si no puedes confirmar PLP, no vendas “commits durables.” Aun puedes usar un dispositivo rápido, pero sé honesto sobre el riesgo.
Task 5: Confirmar ashift y alineación (asesino silencioso del rendimiento)
cr0x@server:~$ zdb -C tank | egrep -n 'ashift|vdev_tree' -A2
42: ashift: 12
43: asize: 1999844147200
44: is_log: 0
Significado: ashift=12 significa sectores de 4K. Si estás en SSD/NVMe moderno, eso suele ser correcto. Un ashift incorrecto puede causar amplificación de escritura y latencia.
Decisión: Si ashift es demasiado pequeño (p. ej., 9) en dispositivos nativos de 4K, la solución requiere reconstruir el pool. No “tunes” alrededor de eso; es estructural.
Task 6: Vigilar el comportamiento de sync de TXG y stalls
cr0x@server:~$ sudo dmesg -T | egrep -i 'txg|spa_sync|zil' | tail -n 5
[Mon Dec 25 10:11:03 2025] ZFS: spa_sync: tank txg 93421 took 2112ms
[Mon Dec 25 10:11:08 2025] ZFS: spa_sync: tank txg 93422 took 1897ms
Significado: TXG syncs que tardan ~2 segundos no son inherentemente fatales, pero pueden correlacionar con picos de latencia, especialmente cuando el sistema está cerca de límites de datos sucios.
Decisión: Si los tiempos de sync de TXG son frecuentemente de varios segundos bajo carga, investiga la latencia de dispositivos del pool, tunables de datos sucios y si las escrituras síncronas se están acumulando detrás de congestión.
Task 7: Comprobar propiedades de ZFS que importan para datasets de bases de datos
cr0x@server:~$ zfs get -o name,property,value -s local,default recordsize,logbias,atime,compression,primarycache tank/db
NAME PROPERTY VALUE SOURCE
tank/db recordsize 16K local
tank/db logbias latency local
tank/db atime off local
tank/db compression lz4 local
tank/db primarycache all default
Significado: recordsize afecta bloques de datos (no registros del ZIL), pero importa para patrones de I/O de tablas. logbias=latency favorece patrones de uso del ZIL buenos para baja latencia. atime=off evita escrituras extras. lz4 suele ser una ganancia neta.
Decisión: Para datasets WAL, considera recordsize más pequeño (p. ej., 16K) y logbias=latency. Para datos, alinea recordsize con el tamaño típico de página y patrones de lectura. No cargo-cultives recordsize=8K en todas partes.
Task 8: Ejecutar una prueba “tipo WAL” con fsync por escritura (peor caso síncrono)
cr0x@server:~$ fio --name=wal-fsync --filename=/tank/db/fio-wal.log --size=8g --bs=8k --rw=write --ioengine=psync --direct=0 --fsync=1 --numjobs=1 --runtime=120 --time_based=1 --group_reporting=1
wal-fsync: (groupid=0, jobs=1): err= 0: pid=18421: Mon Dec 25 10:22:11 2025
write: IOPS=3400, BW=26.6MiB/s (27.9MB/s)(3192MiB/120001msec)
slat (usec): min=6, max=310, avg=11.2, stdev=5.4
clat (usec): min=120, max=8820, avg=280.4, stdev=210.1
lat (usec): min=135, max=8840, avg=292.1, stdev=210.5
clat percentiles (usec):
| 50.00th=[ 240], 95.00th=[ 520], 99.00th=[ 1200], 99.90th=[ 4100]
Significado: Esto modela “escribir 8K, fsync, repetir.” Los percentiles de latencia muestran la ruta de durabilidad. p99.9 en 4ms puede ser aceptable o no dependiendo de tu SLA.
Decisión: Si p99/p99.9 son demasiado altos, estás cazando latencia del SLOG, comportamiento de flush del dispositivo o contención del pool. No toques aún las configuraciones de la base de datos; demuestra primero el almacenamiento.
Task 9: Ejecutar una prueba O_DSYNC (más cercana a algunos modos de BD)
cr0x@server:~$ fio --name=wal-dsync --filename=/tank/db/fio-wal-dsync.log --size=8g --bs=8k --rw=write --ioengine=psync --direct=1 --dsync=1 --numjobs=1 --runtime=120 --time_based=1 --group_reporting=1
wal-dsync: (groupid=0, jobs=1): err= 0: pid=18502: Mon Dec 25 10:25:10 2025
write: IOPS=5200, BW=40.6MiB/s (42.6MB/s)(4872MiB/120001msec)
clat (usec): min=95, max=6210, avg=190.7, stdev=160.2
clat percentiles (usec):
| 50.00th=[ 160], 95.00th=[ 380], 99.00th=[ 900], 99.90th=[ 2700]
Significado: direct=1 evita el page cache; dsync=1 solicita semánticas de sync solo de datos por escritura. Esto a menudo mapea mejor a patrones de “append de log durable” que fsync=1.
Decisión: Si esto es drásticamente mejor que fsync por escritura, puede indicar que tu carga se beneficia de group commit o de distintos primitivas de sync. Valida contra el modo real de tu base de datos.
Task 10: Añadir concurrencia para modelar presión de group commit
cr0x@server:~$ fio --name=wal-dsync-8jobs --filename=/tank/db/fio-wal-8jobs.log --size=16g --bs=8k --rw=write --ioengine=psync --direct=1 --dsync=1 --numjobs=8 --runtime=180 --time_based=1 --group_reporting=1
wal-dsync-8jobs: (groupid=0, jobs=8): err= 0: pid=18614: Mon Dec 25 10:29:40 2025
write: IOPS=24000, BW=187MiB/s (196MB/s)(33660MiB/180001msec)
clat (usec): min=110, max=20210, avg=285.9, stdev=540.3
clat percentiles (usec):
| 50.00th=[ 210], 95.00th=[ 650], 99.00th=[ 2400], 99.90th=[12000]
Significado: El throughput subió, pero p99.9 explotó. Eso es típico cuando el dispositivo de log o el pool no pueden mantener la latencia de cola estrecha bajo concurrencia.
Decisión: Si tu SLA de base de datos es sensible a latencia, optimiza para la latencia de cola, no para IOPS pico. Puede que prefieras menos committers concurrentes o un SLOG mejor en vez de más hilos.
Task 11: Confirmar que fio está emitiendo las syscalls que crees (strace)
cr0x@server:~$ sudo strace -f -e trace=pwrite64,write,fdatasync,fsync,openat fio --name=wal-fsync --filename=/tank/db/trace.log --size=256m --bs=8k --rw=write --ioengine=psync --direct=0 --fsync=1 --numjobs=1 --runtime=10 --time_based=1 --group_reporting=1 2>&1 | tail -n 12
openat(AT_FDCWD, "/tank/db/trace.log", O_RDWR|O_CREAT, 0644) = 3
pwrite64(3, "\0\0\0\0\0\0\0\0"..., 8192, 0) = 8192
fsync(3) = 0
pwrite64(3, "\0\0\0\0\0\0\0\0"..., 8192, 8192) = 8192
fsync(3) = 0
Significado: Puedes ver el patrón real: pwrite, fsync, repetir. Si esperabas O_DSYNC y no lo ves, tus opciones de fio no están haciendo lo que crees.
Decisión: No aceptes “fio dice dsync=1” como prueba. Verifica las syscalls, especialmente al cambiar ioengine o flags de direct I/O.
Task 12: Observar actividad ZIL/SLOG durante la prueba (iostat en vdev de log)
cr0x@server:~$ iostat -x 1 /dev/nvme2n1 /dev/nvme3n1
Linux 6.8.0 (server) 12/25/2025 _x86_64_ (32 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
5.22 0.00 2.10 7.11 0.00 85.57
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz aqu-sz %util
nvme2n1 0.0 0.0 0.0 0.0 0.00 0.0 18500.0 150000.0 0.0 0.0 0.18 8.1 3.2 91.0
nvme3n1 0.0 0.0 0.0 0.0 0.00 0.0 18480.0 149800.0 0.0 0.0 0.19 8.1 3.1 90.4
Significado: Escrituras intensas en los dispositivos de log mientras se ejecuta fio de escrituras síncronas sugiere que el ZIL está golpeando el SLOG. Un w_await bajo es lo que quieres.
Decisión: Si los dispositivos de log muestran poca actividad durante pruebas “síncronas”, probablemente no estés haciendo I/O síncrono, o el dataset/pool está configurado de una manera inesperada.
Task 13: Revisar latencia y encolamiento a nivel de pool (la pregunta “¿son los vdevs principales?”)
cr0x@server:~$ iostat -x 1 /dev/nvme0n1 /dev/nvme1n1
Device r/s rkB/s r_await w/s wkB/s w_await aqu-sz %util
nvme0n1 50.0 12000.0 0.45 1200.0 98000.0 3.90 6.1 99.0
nvme1n1 45.0 11000.0 0.50 1180.0 97000.0 4.10 6.2 99.0
Significado: Los dispositivos del pool principal están saturados (%util cerca de 99) con esperas de escritura de varios milisegundos. Incluso con un SLOG bueno, el sync de TXG y la contención general pueden empujar la latencia de cola hacia arriba.
Decisión: Si los vdevs principales están al máximo, necesitas reducir la presión de escritura de fondo (checkpoints, compactación, otros tenants), añadir vdevs o mover cargas. Un SLOG no puede arreglar un pool que se está ahogando.
Task 14: Asegurarte de que no estás benchmarkeando ARC (cache) para lecturas
cr0x@server:~$ arcstat 1 3
time read miss miss% dmis dm% pmis pm% mmis mm% arcsz c
10:35:01 412 20 4 0 0 20 100 0 0 28.1G 30.0G
10:35:02 398 17 4 0 0 17 100 0 0 28.1G 30.0G
10:35:03 420 18 4 0 0 18 100 0 0 28.1G 30.0G
Significado: Un miss% bajo significa que las lecturas se sirven mayormente desde ARC. Eso está bien para “hits del buffer cache de la base de datos”, pero no es representativo del rendimiento de lectura en disco.
Decisión: Para benchmarks de lectura, dimensiona la prueba para exceder ARC o usa primarycache=metadata en un dataset de prueba para evitar números que sean solo cache.
Task 15: Probar con sync=always temporalmente para detectar “async disfrazado de sync”
cr0x@server:~$ sudo zfs set sync=always tank/db
cr0x@server:~$ zfs get -o name,property,value sync tank/db
NAME PROPERTY VALUE
tank/db sync always
Significado: ZFS tratará todas las escrituras como síncronas en ese dataset. Esto es una palanca de diagnóstico: si el rendimiento cambia dramáticamente, tu carga no estaba emitiendo realmente escrituras síncronas como pensabas, o estaba siendo bufferizada inesperadamente.
Decisión: Usa esto solo en pruebas controladas. Si “sync always” desploma tu throughput, es señal de que tu aplicación depende de comportamiento async y necesitas validar las suposiciones de durabilidad.
Task 16: Confirmar que ZFS no está limitando silenciosamente por límites de datos sucios
cr0x@server:~$ cat /proc/spl/kstat/zfs/arcstats | egrep 'dirty_data|dirty_over'
dirty_data 4 1328619520
dirty_over_target 4 0
Significado: Si dirty_over_target está frecuentemente distinto de cero, ZFS está presionando a los escritores para controlar memoria y comportamiento de sync. Eso se manifiesta como stalls de escritura y picos de latencia.
Decisión: Si alcanzas límites de datos sucios bajo carga normal, investiga dimensionamiento de memoria, patrones de escritura de fondo y tunables de ZFS—con cuidado. No solo subas límites y lo llames día.
Task 17: Validar que tu job de fio no está sobrescribiendo los mismos bloques (ilusión por trimmed)
cr0x@server:~$ fio --name=wal-dsync-rand --filename=/tank/db/fio-wal-rand.log --size=8g --bs=8k --rw=randwrite --ioengine=psync --direct=1 --dsync=1 --numjobs=1 --runtime=60 --time_based=1 --group_reporting=1
wal-dsync-rand: (groupid=0, jobs=1): err= 0: pid=19311: Mon Dec 25 10:41:07 2025
write: IOPS=2100, BW=16.4MiB/s (17.2MB/s)(984MiB/60001msec)
clat percentiles (usec):
| 50.00th=[ 330], 95.00th=[ 1200], 99.00th=[ 4800], 99.90th=[21000]
Significado: Las escrituras síncronas aleatorias son más duras que los appends secuenciales de log. Si tu “prueba WAL” es aleatoria, estás modelando otra cosa (como sync de archivos de datos).
Decisión: Usa secuencial para pruebas tipo WAL a menos que tu base de datos realmente escriba bloques de log de forma no secuencial (raro). Mantén esta prueba como un generador de estrés, no como el modelo primario.
Guion de diagnóstico rápido
Estás en una llamada. Alguien dice “commits son lentos” y pega un único gráfico. Aquí está el camino más corto para identificar el cuello de botella sin convertirlo en un proyecto de arqueología.
Primero: prueba que sea realmente síncrono y realmente durable
-
Comprueba
syncdel dataset y cualquier herencia desde datasets padres.
Si encuentrassync=disabled, para la discusión de rendimiento y empieza la discusión de riesgo. -
Confirma las propias configuraciones de durabilidad de la base de datos (p. ej., WAL fsync activado, modo InnoDB). Si la BD está en “durabilidad relajada”,
no hagas benchmark de “durabilidad estricta” y viceversa. -
Traza una escritura representativa con
straceo trazado a nivel de base de datos para confirmar las syscalls: ¿fsync? ¿fdatasync? ¿O_DSYNC?
Segundo: aislar si el dolor es latencia ZIL/SLOG o congestión del pool
-
Mientras ejecutas un job sync con fio, vigila
iostat -xen los dispositivos SLOG. Si están ocupados yw_awaites alto, tu dispositivo de log es el problema. - Si el SLOG parece bien, mira los dispositivos vdev principales. Si están saturados o con alta latencia, el sync de TXG y la contención del pool están arrastrando todo.
- Busca picos en tiempos de sync de TXG en los logs del kernel. Tiempos de sync de varios segundos correlacionan fuertemente con problemas de latencia de cola.
Tercero: sigue los amplificadores habituales (importan más de lo que quieres)
- Revisa ashift, layout RAID y si el pool está casi lleno. Un pool por encima del ~80–85% de uso suele ver peor fragmentación y comportamiento de asignación.
- Busca cargas competidoras: snapshots, replicación, scrubs, backups, compactaciones, otros datasets en el mismo pool.
- Verifica que el SLOG sea seguro frente a pérdida de energía. Si no lo es, no puedes confiar en él para la durabilidad de la base de datos—el rendimiento es el argumento equivocado.
Tres microhistorias corporativas desde las trincheras de confiabilidad
1) Incidente causado por una suposición equivocada: “sync=disabled es solo más rápido”
Una compañía SaaS mediana migró de una base de datos gestionada a Postgres autoalojado para ahorrar costos y ganar control. El almacenamiento era ZFS sobre un par de mirrors SSD. Un ingeniero bien intencionado puso sync=disabled en el dataset porque los commits eran “lentos en benchmarks.”
Durante semanas, se veía genial. La latencia bajó. Las gráficas estaban verdes. El equipo se dio un paseo de victoria y siguió con trabajo más visible, como features de producto y crear dashboards sobre dashboards.
Entonces ocurrió un evento de energía—no un incendio dramático en el datacenter, solo una cadena de fallos rutinaria: mantenimiento del UPS más un disparo de breaker que no conmutó como todos asumían. Los hosts se reiniciaron. Postgres arrancó. Incluso aceptó conexiones.
El horror sutil fue la corrupción lógica. Un pequeño conjunto de transacciones recientemente confirmadas faltaban, y otro conjunto estaba parcialmente aplicado. La aplicación vio rarezas con foreign keys y los jobs de reconciliación empezaron a eliminar “duplicados” que no eran duplicados.
Tomó días desenmarañarlo, mayormente porque la corrupción raramente es lo suficientemente educada como para crashear inmediatamente.
El postmortem no fue complicado. La suposición equivocada fue: “ZFS es seguro, así que deshabilitar sync es seguro.” ZFS es seguro cuando mantienes su contrato de seguridad. Le pidieron a ZFS mentir a la base de datos. ZFS cumplió. Las computadoras obedecen así.
2) Optimización que salió mal: “pongamos un SLOG barato y rápido”
Una plataforma de servicios financieros tenía un pool ZFS en SSDs empresariales, pero cargas heavy en sync (auditoría más una base de datos transaccional) mostraban picos de latencia en p99. Alguien sugirió añadir un SLOG. Buena intuición.
Procurement se involucró. Encontraron unidades NVMe de consumidor “muy rápidas” a una fracción del costo de modelos empresariales. La ficha técnica era un arcoíris de números IOPS que hacen creer brevemente a ejecutivos que la física es opcional.
El equipo instaló las unidades como SLOG en mirror y volvió a ejecutar fio. Los números parecían fenomenales. Luego llegó el tráfico de producción. La latencia de cola empeoró. No constantemente—lo suficiente como para arruinar SLAs intermitentemente.
El fallo fue doble. Primero, esas unidades tenían comportamiento de caché SLC agresivo: los bursts eran geniales, el logging síncrono sostenido bajo concurrencia no tanto. Segundo, carecían de protección significativa contra pérdida de energía, así que el equipo no podía afirmar honestamente durabilidad. Mejoraron la media y envenenaron el p99, además de aumentar el riesgo.
La solución fue aburrida: reemplazar el SLOG por dispositivos PLP-capables escogidos por baja latencia en escrituras síncronas bajo flush, no por throughput máximo. El rendimiento mejoró y la historia de riesgo dejó de ser embarazosa.
3) Práctica correcta y aburrida que salvó el día: “probar la ruta real de fsync”
Una gran empresa corría una flota multi-tenant MySQL sobre ZFS. Tenían una regla interna: cualquier cambio de almacenamiento requería una “prueba de ruta de durabilidad.”
No un benchmark genérico. Una prueba que modelara su método real de flush y comportamiento de commit.
Un equipo planeó una renovación de plataforma: nuevos servidores, nuevo pool NVMe, nuevo SLOG. En papel era una mejora. Durante pruebas pre-prod, los benchmarks de throughput estándar se veían geniales. La prueba de ruta de durabilidad con fio se veía… rara. Las latencias p99.9 eran inesperadamente altas.
Porque tenían la regla, también tenían las herramientas: jobs de fio que coincidían con su patrón de flush de MySQL, verificación con strace y un script iostat para confirmar que el SLOG estaba siendo golpeado. Rastrearon rápido a un ajuste de firmware en los dispositivos de log que alteraba el comportamiento de flush bajo profundidad de cola.
El proveedor arregló el firmware/ajuste. La plataforma salió. Nadie celebró, que es la cantidad correcta de celebración por “no lanzamos un riesgo latente de datos a producción.”
El ahorro no fue una línea en el presupuesto; fue la ausencia de un postmortem. Esos son los mejores.
Errores comunes: síntomas → causa raíz → solución
1) “fio muestra 200k IOPS, pero la base de datos confirma a 2k/s”
Síntomas: los números de fio son enormes; la latencia de commit de la BD sigue siendo mala; las gráficas no correlacionan.
Causa raíz: la prueba fio está en buffer y no fuerza durabilidad (direct=0 + sin fsync/dsync), o dataset sync=disabled en la prueba pero no en prod.
Solución: Re-ejecuta fio con fsync=1 o dsync=1 (coincidiendo con el modo BD), verifica con strace y confirma zfs get sync en el dataset objetivo.
2) “Agregar un SLOG empeoró las cosas”
Síntomas: la latencia media mejoró, pero p99/p99.9 empeoraron; stalls intermitentes; a veces el SLOG queda al máximo.
Causa raíz: el dispositivo SLOG tiene mala latencia sostenida en escrituras síncronas, carece de PLP, o sufre bajo concurrencia por comportamiento interno de caché.
Solución: Usa dispositivos de baja latencia con PLP; haz mirror del SLOG; valida con pruebas fio concurrentes; vigila iostat -x para w_await y comportamiento de profundidad de cola.
3) “Las escrituras síncronas son lentas incluso con un buen SLOG”
Síntomas: el SLOG se ve bien; los dispositivos del pool principal muestran alta utilización; tiempos de sync de TXG largos.
Causa raíz: congestión del pool (flushes de checkpoint, otros tenants, scrubs, replicación), throttling por datos sucios, o pool casi lleno/fragmentado.
Solución: Reduce escrituras competidoras, agenda scrubs/replicación apropiadamente, añade vdevs, mantén espacio libre en pools y valida tiempos de sync de TXG.
4) “La latencia está bien en pruebas cortas, terrible durante horas”
Síntomas: pruebas de 1–2 minutos se ven geniales; cargas sostenidas degradan; picos periódicos.
Causa raíz: agotamiento de caché SLC del dispositivo, throttling térmico, mantenimiento de fondo (GC), o ciclos TXG/datos sucios no capturados por ejecuciones cortas.
Solución: Ejecuta pruebas más largas (10–20 minutos), monitorea temperatura y throttling del dispositivo, sigue p99.9 y prueba en niveles de llenado en estado estable.
5) “Toggeamos sync=always y el rendimiento se desplomó”
Síntomas: el throughput cae masivamente; la latencia salta; la app comienza a timeoutear.
Causa raíz: la carga dependía de escrituras async; las suposiciones de durabilidad de la app eran más débiles de lo esperado; o el dispositivo de log/pool no puede sostener comportamiento forzado de sync.
Solución: Alinea las expectativas de durabilidad con los requisitos del negocio, confirma ajustes de la BD, implementa un SLOG apropiado o acepta tasas de commit más bajas con durabilidad correcta.
6) “Las escrituras síncronas aleatorias son catastróficas”
Síntomas: randwrite + dsync muestra latencia de cola terrible; pruebas secuenciales se ven bien.
Causa raíz: estás probando patrones de sync de archivos de datos (difícil) en lugar de append de log (más fácil). También posible: amplificación de escritura RAIDZ y fragmentación.
Solución: Separa pruebas WAL (secuencial) de pruebas de datos; considera mirrors para cargas DB sensibles a latencia; mantén headroom en pool y recordsize sensato.
Listas de verificación / plan paso a paso
Paso a paso: construir un benchmark de escrituras síncronas que puedas defender en un postmortem
-
Elige el modo de durabilidad de la base de datos.
Escribe exactamente cómo se hacen los commits (¿fsync por commit? ¿group commit? ¿O_DSYNC?). -
Bloquea propiedades de ZFS.
Confirmasync,logbias,compression,atimey cualquier herencia desde padres. -
Confirma presencia y tipo de SLOG.
zpool status, identifica vdevs de log, confirma mirroring, confirma la historia PLP. -
Construye dos perfiles de fio:
uno para escrituras de log síncronas (secuencial 8–16K), otro para datos/checkpoint (más grande, mixto, posiblemente aleatorio). -
Verifica syscalls.
Usastraceen una ejecución corta. Si no emite fsync/dsync como piensas, para y arregla el job. -
Ejecuta lo bastante.
Al menos 2–5 minutos; más tiempo si te importan p99.9. -
Recolecta las métricas correctas mientras corre:
iostat -xpara SLOG y vdevs principales, tiempos de sync TXG en logs, CPU iowait y percentiles de latencia de fio. -
Toma una decisión basada en p99/p99.9.
Si la latencia de cola es inaceptable, trátalo como un problema de diseño de almacenamiento, no como “ajusta la BD hasta que deje de quejarse”.
Checklist: sentido común del SLOG para bases de datos
- Dispositivos con capacidad PLP (o aceptación documentada del riesgo).
- Hacer mirror del SLOG para disponibilidad y reducir escenarios de pérdida de log por dispositivo único.
- Optimizar para consistencia de latencia, no para números de throughput de marketing.
- Monitorizar la latencia de escritura del SLOG bajo concurrencia.
- Mantenerlo simple: un buen SLOG supera a tres dudosos.
Checklist: cuándo sospechar que te estás autoengañando
- Tu job de fio corre “demasiado rápido” y los dispositivos de log no muestran escrituras.
- Pruebas cortas se ven increíbles; pruebas largas degradan drásticamente.
- La latencia promedio se ve bien, pero la base de datos está haciendo timeouts (latencia de cola).
- Cambiaste
synco ajustes de flush de BD “por rendimiento” y no ejecutaste validación de escenario de pérdida de energía. - No puedes explicar qué patrón de syscalls usa tu benchmark.
FAQ
1) ¿Debo usar fsync=1 o dsync=1 en fio para probar logs de bases de datos?
Usa lo que tu base de datos usa. PostgreSQL comúnmente se parece a escrituras en buffer más fsync. Algunos modos de MySQL se parecen a patrones O_DSYNC u O_DIRECT.
Si no lo sabes, traza la base de datos con strace durante commits y empata eso.
2) ¿Es sync=disabled alguna vez aceptable para bases de datos?
Solo si estás eligiendo explícitamente durabilidad más débil (y el negocio está de acuerdo). Puede ser aceptable para caches efímeros o datos reconstruibles.
Para sistemas transaccionales, es una degradación de durabilidad disfrazada de perilla de tuning.
3) ¿Un SLOG mejora el throughput de escrituras asíncronas normales?
No. Un SLOG trata operaciones síncronas. Si tu carga es mayormente escrituras asíncronas, un SLOG no ayudará mucho. Aun puede ayudar si tu
base de datos hace fsyncs frecuentes, que es exactamente su punto.
4) ¿Qué tamaño debería tener mi SLOG?
Usualmente más pequeño de lo que piensas. Dimensionar el SLOG es sobre amortiguar unos segundos de intención de escrituras síncronas, no almacenar tu base de datos. El requisito real es escrituras durables de baja latencia bajo carga sostenida. Sobredimensionar capacidad no arregla mala latencia.
5) ¿Por qué la concurrencia aumenta IOPS pero empeora la p99.9?
Estás cambiando latencia por operación por throughput. Más jobs incrementan la profundidad de cola, lo que puede suavizar la utilización pero amplificar el comportamiento de cola,
especialmente si el dispositivo tiene rarezas de caché interno o el pool está saturado.
6) ¿Afecta recordsize al rendimiento de escrituras síncronas?
No directamente para registros del ZIL, pero afecta cómo se escriben los bloques de datos y puede cambiar el comportamiento de checkpoint y read-modify-write. Para datasets de log,
las perillas clave son sync, calidad del SLOG y salud general del pool.
7) ¿Debería usar direct=1 para benchmarks WAL?
Depende. Si tu base de datos escribe WAL en buffer y fsyncea, entonces direct=0 con fsync lo modela mejor. Si la base de datos usa I/O directo u O_DSYNC, direct=1 puede ser apropiado. La respuesta correcta es: empata la realidad y luego mide.
8) ¿Es necesario mirror del SLOG?
Para bases de datos serias, sí, si vas a usar un SLOG. Perder un dispositivo de log en el momento equivocado puede convertir un crash en una recuperación más larga o, en el peor de los casos, pérdida de datos de operaciones síncronas recientes según el timing y el modo de fallo. Hacer mirror es un seguro barato comparado con explicar transacciones perdidas.
9) ¿Por qué mis números de fio cambian tras un reboot?
Warm-up de cache, distinto estado térmico del dispositivo, estado de GC en SSDs y distinta fragmentación/localidad de metadata importan. Por eso pruebas largas en condiciones de estado estable son más honestas que “una corrida tras el arranque.”
10) ¿Puede RAIDZ ser bueno para bases de datos en ZFS?
Puede estar bien para cargas orientadas a lectura o throughput, pero bases de datos sensibles a latencia y heavy en sync suelen preferir mirrors porque las escrituras pequeñas y la latencia de cola se comportan mejor. Si tu prioridad es latencia de commit, los mirrors son la opción por defecto por una razón.
Conclusión: próximos pasos prácticos
Si quieres probar ZFS para bases de datos honestamente, deja de perseguir el número de throughput más grande y empieza a perseguir el perfil de latencia más aburrido, repetible y correcto en durabilidad que puedas obtener.
Próximos pasos que realmente mueven la aguja:
- Elige el modo de durabilidad de la base de datos que usas en producción y escríbelo (cadencia de fsync, group commit, método de flush).
- Verifica la propiedad de dataset
syncy la configuración del SLOG, y demuestra el comportamiento de syscalls constrace. - Ejecuta dos perfiles de fio (WAL sync y datos/checkpoint) lo bastante largos para capturar latencia de cola y ciclos TXG.
- Cuando los resultados sean malos, usa el guion de diagnóstico rápido: valida semánticas de sync, aísla SLOG vs pool y luego caza contención y amplificadores.
- Si necesitas un SLOG, cómpralo por PLP y consistencia de latencia. Si no puedes justificar eso, sé honesto sobre los trade-offs de durabilidad.
La ingeniería de rendimiento consiste mayormente en negarse a impresionar por tus propias gráficas. ZFS hará lo que le pidas. Asegúrate de pedir la verdad.