Debian 13: los benchmarks de fio mienten — cómo probar almacenamiento sin engañarte

¿Te fue útil?

Ejecutaste fio en Debian 13, obtuviste cifras heroicas y declaraste victoria. Luego en producción todo se vino abajo durante el primer reindexado, copia de seguridad o compactación, y los discos “rápidos” de repente se comportaron como un disco de red compartido de 2008.

Esto es normal. No porque el almacenamiento en Linux sea malo, sino porque los benchmarks son fáciles de manipular por accidente. fio no miente; nosotros nos engañamos con suposiciones erróneas, valores por defecto convenientes y pruebas que no coinciden con la carga real.

Qué mide realmente fio (y qué no)

fio es un generador de carga. Envía E/S con patrones específicos (aleatorio vs secuencial, lectura vs escritura, mixto, tamaños de bloque, profundidad de cola, modos de sincronización) y reporta lo que ocurrió. Eso es todo. No sabe si probaste la caché de páginas en lugar del disco. No sabe si tu SSD está a punto de reducir frecuencia por calor. No sabe si tu controlador RAID está confirmando escrituras que aún viven en caché volátil. No sabe que tu base de datos hace lecturas aleatorias de 4k mientras tu benchmark hizo escrituras secuenciales de 1M.

Para evaluar almacenamiento sin engañarte, necesitas responder tres preguntas desde el principio:

  • ¿Qué intentas predecir? ¿Rendimiento pico? ¿Latencia P99 bajo concurrencia? ¿Tiempo de recuperación? ¿Comportamiento de cola durante ráfagas de escritura?
  • ¿Cuál es la unidad de verdad? IOPS no es la verdad si a tus usuarios les importa la latencia. El throughput no es la verdad si tu cuello de botella son escrituras sincronizadas de bloques pequeños.
  • ¿Qué sistema estás evaluando? Disco solo, pila del SO, sistema de archivos, gestor de volúmenes, cifrado, almacenamiento en red, caché del controlador y planificación de CPU importan. Decide qué pertenece al alcance.

Los benchmarks no deben ser “justos”. Deben ser predictivos. Si tu prueba no coincide con la forma de E/S en producción, es un generador de confianza, no un benchmark.

Hechos y contexto interesantes (lo que hace predecibles los errores de hoy)

  • La caché de páginas de Linux ha sido un “amplificador de benchmarks” desde los 90. La E/S en búfer puede medir la velocidad de la RAM y parecer velocidad de disco si no forzas I/O directo o no superas la memoria.
  • El rendimiento de los SSD depende del estado interno. La NAND recién salida de caja se comporta distinto que en estado estable tras escrituras sostenidas; el preacondicionamiento se convirtió en un concepto formal porque las primeras revisiones de SSD eran básicamente fantasía.
  • La profundidad de cola se popularizó con los SAN empresariales. Mucho folklore de “QD32” viene de la era de controladores; no es automáticamente relevante para NVMe de baja latencia y aplicaciones modernas con concurrencia limitada.
  • NVMe cambió el lado CPU del almacenamiento. Las rutas de envío/completado de I/O se volvieron más baratas y paralelas, por lo que el pinning de CPU y la distribución de IRQ pueden convertirse en el “cuello de botella del disco” aun cuando el medio está bien.
  • El caching de escrituras siempre fue polémico. Los controladores que confirman escrituras antes de que sean duraderas dieron al mundo grandes benchmarks y algún que otro apagón memorable; la caché con batería fue el compromiso.
  • Los sistemas de archivos históricamente optimizaron para distintos modos de fallo. Los valores por defecto de ext4 buscan seguridad amplia; XFS suele destacar en throughput paralelo; los sistemas copy-on-write intercambian ciertas latencias por snapshots y sumas de verificación.
  • Los problemas de alineación son anteriores a los SSD. Particiones mal alineadas perjudicaban en unidades RAID y siguen afectando hoy cuando tus bloques lógicos de 4k no coinciden con la geometría subyacente.
  • Los “percentiles de latencia” pasaron de la academia a las operaciones. La industria aprendió a las malas que los promedios ocultan el dolor; P99 y P99.9 se volvieron operativamente significativos a medida que crecieron los sistemas a escala web.

Una cita para tener en mente mientras haces benchmarks: La esperanza no es una estrategia. (Vince Lombardi)

Cómo los benchmarks hacen trampa sin querer: los sospechosos habituales

1) Hiciste benchmark de la RAM (caché de páginas), no del almacenamiento

Si ejecutas jobs de fio basados en archivos sin --direct=1, Linux puede satisfacer lecturas desde caché y absorber escrituras en memoria para luego volcarlas. Tus resultados se ven increíbles. Tu base de datos no obtiene esos números cuando tiene que esperar por durabilidad real.

Y no, “pero mi archivo de prueba era grande” no siempre basta. Las cachés existen en múltiples capas: caché de páginas, caché del disco, caché del controlador, incluso el hipervisor si estás virtualizado.

2) El tamaño de solicitud y la semántica de sincronización no coinciden con la realidad

Muchos sistemas de producción realizan escrituras pequeñas y sincronizadas (commits de journal, WAL fsync, actualizaciones de metadatos). Un benchmark que hace escrituras secuenciales de 1M con profundidad de cola profunda está midiendo otro universo.

3) La profundidad de cola se vuelve un disfraz de rendimiento

Un alto iodepth puede ocultar latencia manteniendo ocupado el dispositivo e hinchando throughput e IOPS. Eso puede ser legítimo—si tu aplicación realmente emite tantas solicitudes pendientes. Si no, estás probando un sistema que no ejecutas.

4) El dispositivo está en un estado “limpio” que la producción nunca ve

El comportamiento del FTL del SSD depende de bloques libres y la presión de recolección de basura. Pruebas cortas en unidades vacías pueden ser irrealmente rápidas. Pruebas largas pueden volverse lentas. Ambas son verdad; solo una es predictiva.

5) Mediste un camino distinto al que usa tu carga

Las pruebas a nivel de bloque pueden evitar la sobrecarga del sistema de archivos, journaling, opciones de montaje y la contención de metadatos. Las pruebas basadas en archivos incluyen eso—pero también incluyen la disposición de directorios y el comportamiento del espacio libre. Elige con intención.

6) CPU, IRQs y NUMA se convierten silenciosamente en “rendimiento de almacenamiento”

En NVMe es común que el cuello de botella sea el manejo de interrupciones, la contención de colas únicas o una mala afinidad. Tu “benchmark de disco” se convierte en un benchmark de planificación de CPU. Eso no es incorrecto—a menos que no lo notes.

7) Gestión de energía, throttling y políticas de firmware cambian las reglas a mitad de prueba

Los SSD y las unidades NVMe pueden reducir su rendimiento por temperatura. Las CPUs pueden bajar frecuencia. Los portátiles hacen cosas de portátil. Los servidores hacen cosas de ahorro de energía. Tu benchmark se convierte en una prueba de disipadores, no de IOPS.

Broma corta #1: Un benchmark de almacenamiento sin percentiles de latencia es como una crítica de restaurante que solo indica el tiempo medio de espera—felicitaciones por tu hambre estadísticamente satisfactoria.

Tareas prácticas: 12+ comprobaciones con comandos, significado de la salida y la decisión que tomas

Estas no son “agradables de tener”. Así evitas que fio se convierta en teatro de rendimiento en Debian 13.

Task 1: Verify what device you’re testing (and whether it’s virtual)

cr0x@server:~$ lsblk -o NAME,MODEL,SIZE,ROTA,TRAN,TYPE,MOUNTPOINTS
NAME        MODEL               SIZE ROTA TRAN TYPE MOUNTPOINTS
sda         INTEL SSDSC2KB48     447G    0 sata disk
├─sda1                            1G    0      part /boot
└─sda2                          446G    0      part
  └─vg0-lv0                      400G    0      lvm  /
nvme0n1     SAMSUNG MZVL21T0      1.8T    0 nvme disk
└─nvme0n1p1                        1.8T  0      part /data

Qué significa: No puedes interpretar resultados si no sabes si estás en SATA SSD, NVMe, un disco virtual o una capa LVM. ROTA=0 indica no rotacional, pero eso aún incluye SATA SSD y NVMe.

Decisión: Elige pruebas a nivel de dispositivo para comparar medios en crudo; pruebas de archivo para sistema de archivos + opciones de montaje; no mezcles y luego discutas “el disco”.

Task 2: Identify the active I/O scheduler (and avoid accidental queueing behavior)

cr0x@server:~$ cat /sys/block/nvme0n1/queue/scheduler
[mq-deadline] none

Qué significa: El scheduler entre corchetes está activo. NVMe suele funcionar bien con none o mq-deadline, pero el “correcto” depende de tu carga y de los valores por defecto del kernel.

Decisión: Manténlo consistente entre pruebas. Si evalúas para producción, iguala el scheduler de producción. Si diagnosticas latencia extraña, prueba ambos none y mq-deadline.

Task 3: Confirm logical/physical block sizes and alignment risk

cr0x@server:~$ sudo blockdev --getss /dev/nvme0n1
512
cr0x@server:~$ sudo blockdev --getpbsz /dev/nvme0n1
4096

Qué significa: Sectores lógicos de 512 bytes, físicos de 4k. La desalineación puede causar penalizaciones de read-modify-write, especialmente para escrituras pequeñas.

Decisión: Asegura que las particiones comiencen en límites de 1MiB (las herramientas modernas suelen hacerlo). Para bases de datos, alinea el tamaño de página y el tamaño de bloque del sistema de archivos cuando sea posible.

Task 4: Check filesystem and mount options (journaling and barriers matter)

cr0x@server:~$ findmnt -no SOURCE,FSTYPE,OPTIONS /data
/dev/nvme0n1p1 ext4 rw,relatime,errors=remount-ro

Qué significa: Opciones como noatime, barrier (o nobarrier históricamente) y el modo de journaling afectan el comportamiento de escritura.

Decisión: Si mides rendimiento sensible a durabilidad, no cambies opciones “por velocidad” a la ligera a menos que puedas probar la seguridad ante pérdida de energía y la corrección de la aplicación.

Task 5: Detect write cache settings (and whether you’re benchmarking honesty)

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

Qué significa: Caché de escritura “on” puede estar bien para SSD con protección contra pérdida de energía; peligroso para discos de consumo o controladores sin protección si desactivas barriers o falseas flushes.

Decisión: Para bases de datos en producción, exige protección contra pérdida de energía o configuraciones conservadoras. Para benchmarks, anota explícitamente la configuración de caché en tu informe.

Task 6: Observe current device health and thermal state (before blaming fio)

cr0x@server:~$ sudo smartctl -a /dev/nvme0n1 | sed -n '1,40p'
smartctl 7.4 2023-08-01 r5530 [x86_64-linux-6.12.0-amd64] (local build)
=== START OF INFORMATION SECTION ===
Model Number:                       SAMSUNG MZVL21T0
Serial Number:                      S6XXXXXXXXXXXX
Firmware Version:                   3B2QGXA7
Total NVM Capacity:                 1,900,000,000,000 [1.90 TB]
...
Temperature:                        68 Celsius
Available Spare:                    100%
Percentage Used:                    2%

Qué significa: Si la temperatura es alta, el throttling puede activarse a mitad de la prueba. “Percentage Used” ayuda a detectar unidades cerca del fin de su vida útil que se comportan de forma extraña.

Decisión: Si las térmicas están cerca de los límites del fabricante, arregla la refrigeración antes de “tunear fio”. Si la unidad está desgastada, espera peores escrituras sostenidas.

Task 7: Confirm you can bypass page cache (direct I/O sanity check)

cr0x@server:~$ fio --name=direct-check --filename=/data/fio.test --size=2G --rw=read --bs=128k --direct=1 --ioengine=libaio --iodepth=16 --numjobs=1 --runtime=10 --time_based --group_reporting
direct-check: (g=0): rw=read, bs=(R) 128KiB-128KiB, (W) 128KiB-128KiB, (T) 128KiB-128KiB, ioengine=libaio, iodepth=16
fio-3.38
Starting 1 process
direct-check: Laying out IO file (1 file / 2048MiB)
Jobs: 1 (f=1): [R(1)][100.0%][r=912MiB/s][r=7296 IOPS][eta 00m:00s]
direct-check: (groupid=0, jobs=1): err= 0: pid=21199: Mon Dec 29 11:09:10 2025
  read: IOPS=7200, BW=900MiB/s (944MB/s)(9000MiB/10001msec)

Qué significa: --direct=1 solicita I/O directo (evita la caché de páginas). Si lo omites, las lecturas pueden golpear la RAM después del primer pase.

Decisión: Usa --direct=1 para caracterizar el dispositivo. Usa I/O en búfer solo cuando modeles una app que dependa de caché (y entonces mide también el tamaño y comportamiento de la caché).

Task 8: Prove to yourself that buffered reads can cheat

cr0x@server:~$ fio --name=buffered-cheat --filename=/data/fio.test --size=2G --rw=read --bs=128k --direct=0 --ioengine=psync --iodepth=1 --numjobs=1 --runtime=10 --time_based --group_reporting
buffered-cheat: (g=0): rw=read, bs=(R) 128KiB-128KiB, (W) 128KiB-128KiB, (T) 128KiB-128KiB, ioengine=psync, iodepth=1
fio-3.38
Starting 1 process
Jobs: 1 (f=1): [R(1)][100.0%][r=5240MiB/s][r=41920 IOPS][eta 00m:00s]
buffered-cheat: (groupid=0, jobs=1): err= 0: pid=21305: Mon Dec 29 11:11:33 2025
  read: IOPS=41000, BW=5120MiB/s (5370MB/s)(51200MiB/10001msec)

Qué significa: Ese ancho de banda está sospechosamente cerca del comportamiento del ancho de banda de memoria en muchos sistemas. Felicitaciones, hiciste benchmark de la caché.

Decisión: Nunca publiques números de lectura en búfer como “velocidad del disco” sin advertencias explícitas y una metodología de caché fría.

Task 9: Measure latency percentiles (because averages are liars)

cr0x@server:~$ fio --name=latency-4k --filename=/dev/nvme0n1 --rw=randread --bs=4k --direct=1 --ioengine=io_uring --iodepth=32 --numjobs=4 --runtime=30 --time_based --group_reporting --lat_percentiles=1
latency-4k: (g=0): rw=randread, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=io_uring, iodepth=32
fio-3.38
Starting 4 processes
Jobs: 4 (f=4): [r(4)][100.0%][r=1680MiB/s][r=430k IOPS][eta 00m:00s]
latency-4k: (groupid=0, jobs=4): err= 0: pid=21601: Mon Dec 29 11:14:22 2025
  read: IOPS=430k, BW=1680MiB/s (1762MB/s)(50400MiB/30001msec)
    clat percentiles (usec):
     |  1.00th=[   66],  5.00th=[   72], 10.00th=[   76], 50.00th=[   90]
     | 90.00th=[  120], 95.00th=[  140], 99.00th=[  210], 99.90th=[  420]
     | 99.99th=[  920]

Qué significa: La mediana se ve bien, el P99 está aceptable y el P99.99 es donde viven los “picos misteriosos”. La latencia de cola importa para bases de datos, brokers de mensajería y cualquier cosa con commit síncrono.

Decisión: Si P99.9/P99.99 es malo, no apruebes el almacenamiento basándote en el throughput medio. Arregla contención, reduce la profundidad de cola o cambia el medio/controlador.

Task 10: Confirm you’re not CPU-bound during “storage” tests

cr0x@server:~$ mpstat -P ALL 1 5
Linux 6.12.0-amd64 (server) 	12/29/2025 	_x86_64_	(32 CPU)

11:16:01 AM  CPU  %usr %nice %sys %iowait %irq %soft %steal %idle
11:16:02 AM  all  18.2  0.0  31.5    0.3  0.0   9.2    0.0  40.8
11:16:02 AM    7  11.0  0.0  72.0    0.0  0.0  12.0    0.0   5.0

Qué significa: Si uno o dos CPUs están al máximo en %sys/%soft mientras otros están ociosos, podrías estar limitado por una sola cola de IRQ o por la ruta de manejo de completions.

Decisión: Investiga afinidades de IRQ y distribución multi-queue antes de comprar “discos más rápidos” para arreglar un problema de planificación de CPU.

Task 11: Watch real-time disk utilization and latency while fio runs

cr0x@server:~$ iostat -x 1 5
Linux 6.12.0-amd64 (server) 	12/29/2025 	_x86_64_	(32 CPU)

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   w_await wareq-sz  aqu-sz  %util
nvme0n1       98000.0 392000.0     0.0    0.0    0.11     4.00     0.0      0.0     0.00     0.00    12.1   98.5

Qué significa: r_await es una medida aproximada de latencia. %util cerca de 100% indica saturación del dispositivo (pero en NVMe puede ser engañoso; aun así es útil).

Decisión: Si fio afirma IOPS altos pero iostat muestra baja utilización, algo anda mal (caché, objetivo equivocado o fio no está haciendo lo que crees).

Task 12: Inspect NVMe error log and firmware details (silent problems happen)

cr0x@server:~$ sudo nvme id-ctrl /dev/nvme0n1 | grep -E 'mn|fr|oacs|oncs'
mn      : SAMSUNG MZVL21T0
fr      : 3B2QGXA7
oacs    : 0x17
oncs    : 0x5f
cr0x@server:~$ sudo nvme error-log /dev/nvme0n1 | head
Error Log Entries for device:nvme0n1 entries:64
 Entry[ 0]
 .................

Qué significa: Versiones de firmware y logs de error ayudan a correlacionar “caídas extrañas en el benchmark” con problemas a nivel de dispositivo. Algunas unidades tienen peculiaridades conocidas bajo mezclas de comandos específicas.

Decisión: Si ves errores o timeouts, detén la prueba. Los benchmarks no arreglan hardware. Reemplaza/actualiza firmware y vuelve a probar.

Task 13: Precondition / steady-state test for SSDs (so you don’t test the showroom model)

cr0x@server:~$ fio --name=precondition --filename=/dev/nvme0n1 --rw=write --bs=1M --direct=1 --ioengine=io_uring --iodepth=32 --numjobs=1 --runtime=600 --time_based --group_reporting
precondition: (g=0): rw=write, bs=(R) 1024KiB-1024KiB, (W) 1024KiB-1024KiB, (T) 1024KiB-1024KiB, ioengine=io_uring, iodepth=32
fio-3.38
Starting 1 process
Jobs: 1 (f=1): [W(1)][100.0%][w=1450MiB/s][w=1450 IOPS][eta 00m:00s]

Qué significa: Escrituras sostenidas llevan la unidad a un estado más realista: caché SLC agotada, GC activo, características térmicas visibles.

Decisión: Si te importa el estado estable, debes preacondicionar y luego ejecutar la prueba real. Si solo te importa la velocidad de ráfaga, etiquétala explícitamente como ráfaga.

Task 14: Verify TRIM/discard behavior (especially for virtual disks and thin provisioning)

cr0x@server:~$ lsblk -D -o NAME,DISC-GRAN,DISC-MAX,DISC-ZERO
NAME      DISC-GRAN DISC-MAX DISC-ZERO
sda              2M       2G         0
nvme0n1          4K       2G         0

Qué significa: Granularidad/max de discard no cero indica que el dispositivo soporta discard. Si está habilitado depende de las opciones de montaje y del entorno.

Decisión: Si dependes de thin provisioning o del rendimiento a largo plazo de SSD, asegúrate de que la estrategia de discard/TRIM sea intencionada (fstrim periódico vs discard online).

Diseñar jobs de fio que se parezcan a tu carga

La forma más rápida de dejarte engañar es usar el comando fio que encontraste en un post del blog que no mencionaba fsync, percentiles o el tamaño del archivo de prueba relativo a la RAM.

Pick the scope: block device vs filesystem

  • Pruebas de dispositivo de bloque (--filename=/dev/nvme0n1) sirven para características del medio/controlador y la sobrecarga de la ruta de E/S del SO. Evitan metadatos del sistema de archivos y fragmentación. Ideales para comparar dispositivos; menos predictivas para “app sobre ext4”.
  • Pruebas de archivo (--filename=/data/fio.test) incluyen el comportamiento del sistema de archivos. Buenas para opciones de montaje, efectos del journaling y el comportamiento real de asignación de archivos. Pero son más fáciles de cachear por accidente.

Control caching explicitly

Para la mayoría de caracterizaciones de almacenamiento:

  • Usa --direct=1.
  • Usa un tamaño de prueba que no sea “lo suficientemente pequeño para mantenerse caliente”. Si debes hacer pruebas de archivo, considera tamaños mayores que la RAM cuando pruebes lecturas.
  • Registra si corriste en un sistema inactivo o en un nodo compartido. “La copia de seguridad de otro” es una variable de rendimiento.

Choose an IO engine on purpose

En Debian 13, comúnmente usarás:

  • io_uring: moderno, eficiente, bueno para IOPS altas. Puede exponer problemas de CPU/IRQ más rápido.
  • libaio: interfaz clásica de I/O asíncrono para O_DIRECT, sigue siendo ampliamente usada y estable.
  • psync: útil para comportamiento síncrono de un solo hilo; no lo uses por defecto “porque funciona”.

Match concurrency and queue depth to your app

No elijas iodepth=256 porque suena serio. Mide cuántas I/Os pendientes genera realmente tu carga y emula eso. Las bases de datos a menudo tienen un número limitado de lecturas concurrentes, y la latencia suele importar más que el throughput pico al llenar colas.

Use mixed workloads when reality is mixed

Muchos sistemas hacen 70/30 lecturas/escrituras, con distintos tamaños de bloque y semánticas de sincronización. fio puede hacer eso; es tu trabajo especificarlo.

Measure the tail, not just the mean

Los percentiles no son elegantes. Son la parte de la distribución donde tus SLOs van a morir.

Broma corta #2: Si tu informe de benchmark solo tiene “MB/s”, no corriste una prueba—hiciste un chequeo de vibras.

Guía de diagnóstico rápido: encuentra el cuello de botella sin una semana de debate

Esta es la secuencia que uso cuando un sistema “tiene almacenamiento lento” y todos ya están emocionalmente apegados a una teoría.

First: prove what path you’re on

  1. Confirma dispositivo y capas: lsblk, findmnt, busca LVM, mdraid, dm-crypt.
  2. Confirma que golpeas el objetivo correcto: el filename de fio apunta al dispositivo o montaje correcto.
  3. Confirma que la elección entre directo y en búfer coincide con la pregunta.

Second: determine if it’s saturation or stalls

  1. Ejecuta fio con percentiles y concurrencia moderada; observa iostat -x.
  2. Si %util es alto y la latencia sube con iodepth, estás saturando el dispositivo o su ruta de colas.
  3. Si la utilización es baja pero la latencia alta, sospecha contención en otro sitio: CPU, IRQ, locks, sistema de archivos, cifrado o un scheduler mal configurado.

Third: separate CPU/IRQ bottlenecks from media bottlenecks

  1. Revisa distribución de CPU: mpstat.
  2. Revisa afinidad de IRQ y colas NVMe si estás en detalle (no mostrado aquí como guía completa de tuning, pero el síntoma suele ser “un núcleo quemado”).
  3. Prueba con un ioengine distinto o reduce iodepth para ver si la latencia de cola mejora.

Fourth: validate device behavior under sustained load

  1. Preacondiciona si SSD.
  2. Ejecuta pruebas más largas; observa temperatura y throughput con el tiempo.
  3. Revisa SMART/NVMe logs por errores.

Fifth: confirm filesystem and durability semantics

  1. Prueba con patrones síncronos relevantes para la app (fsync, fdatasync o patrones específicos de base de datos).
  2. Valida opciones de montaje; evita atajos tipo “nobarrier” salvo que tengas protección contra pérdida de energía y una razón sólida.

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

1) “Reads are 5 GB/s on SATA SSD”

  • Síntoma: Lecturas en búfer muestran varios GB/s; lecturas directas son mucho más bajas.
  • Causa raíz: La caché de páginas satisfecho las lecturas tras el calentamiento; benchmarkeaste memoria.
  • Solución: Usa --direct=1, metodología de caché fría y/o excede la RAM.

2) “Writes are fast, but database fsync is slow”

  • Síntoma: El throughput de escrituras secuenciales se ve genial; la latencia explota con escrituras síncronas.
  • Causa raíz: El benchmark usó escrituras asíncronas en búfer; la app espera flush/fua/commits de journal.
  • Solución: Ejecuta fio con semánticas síncronas: incluye --fsync=1 o usa --rw=randwrite con bs e iodepth relevantes y mide percentiles.

3) “Random write IOPS collapses after a minute”

  • Síntoma: Números iniciales geniales; el rendimiento sostenido cae en picado.
  • Causa raíz: Agotamiento de la caché SLC del SSD y recolección de basura; dispositivo no preacondicionado.
  • Solución: Preacondiciona (escrituras sostenidas), luego prueba en estado estable; considera sobreaprovisionamiento o SSD empresariales para cargas intensivas en escritura.

4) “Benchmark numbers change between identical runs”

  • Síntoma: Mismo comando fio, resultados distintos día a día.
  • Causa raíz: Carga de fondo, throttling térmico, escalado de frecuencia de CPU, distinta disposición del espacio libre o estado de caché.
  • Solución: Controla el entorno: aísla el host, registra térmicas, fija frecuencia de CPU si procede, preacondiciona y ejecuta múltiples iteraciones con la varianza registrada.

5) “High IOPS but terrible tail latency”

  • Síntoma: Promedio genial; P99.9 doloroso.
  • Causa raíz: Profundidad de cola excesiva, contención en la capa de bloque, picos de GC del firmware o dispositivo compartido.
  • Solución: Reduce iodepth, ajusta a la concurrencia de la app, aisla la prueba y céntrate en percentiles en vez de picos.

6) “It’s fast on raw device, slow on filesystem”

  • Síntoma: Pruebas en /dev/nvme excelentes; pruebas de archivo en /data lentas.
  • Causa raíz: Journaling del sistema de archivos, contención de metadatos, tablas de inodos pequeñas, opciones de montaje o fragmentación.
  • Solución: Benchmarkea intencionalmente ambas capas; ajusta el sistema de archivos para la carga; valida alineación y espacio libre; considera diferencias XFS/ext4 para paralelismo.

7) “Adding encryption barely changed throughput, but latency got weird”

  • Síntoma: Throughput similar; latencia de cola empeoró.
  • Causa raíz: Ruta de cifrado limitada por CPU durante ráfagas; mala afinidad CPU/NUMA; sobrecarga por E/S pequeñas.
  • Solución: Mide uso de CPU durante fio; considera --numjobs y pinning de CPU; asegúrate de AES-NI y modo de cifrado apropiado; mantiene iodepth realista.

Tres microhistorias corporativas desde las trincheras del almacenamiento

Mini-story 1: The incident caused by a wrong assumption

Una compañía SaaS mediana actualizó una flota de nodos de base de datos. Nuevas unidades NVMe, nuevo kernel, instalaciones Debian limpias. La hoja interna de benchmarks se veía fantástica: lecturas aleatorias en cientos de miles de IOPS. El plan de migración se aprobó y programó para un fin de semana tranquilo.

Tras el corte, la base de datos “estuvo bien” unas horas. Luego la latencia subió durante tareas rutinarias: reconstrucción de índices, autovacuum y una cola de tareas en background intensas en escritura. La app no se cayó. Simplemente se volvió prácticamente inutilizable. Llegaron oleadas de tickets de soporte, como ocurre cuando un sistema está vivo pero sufriendo.

La suposición errónea fue sutil: el benchmark utilizó pruebas basadas en archivos en un sistema de archivos vacío con I/O en búfer, y el tamaño de la prueba era lo bastante pequeño como para que la caché de páginas hiciera de héroe. En producción, el dataset de la base de datos excedía la memoria y forzaba lecturas reales. Peor aún, la carga requería comportamiento frecuente tipo fsync; el benchmark no lo incluyó.

La solución no fue “comprar NVMe más rápido”. Fue volver a ejecutar pruebas con --direct=1, concurrencia realista y percentiles de latencia; luego ajustar los límites de concurrencia de I/O de la base de datos para adaptarlos al comportamiento del dispositivo. Cuando dejaron de perseguir el número de IOPS de cabecera y empezaron a apuntar a la latencia P99 bajo iodepth realista, el sistema se estabilizó. Las unidades estaban bien. La narrativa del benchmark no lo estaba.

Mini-story 2: The optimization that backfired

En otra organización corrieron un gran clúster de analítica. Alguien notó que los jobs nocturnos eran más lentos tras una renovación de almacenamiento, a pesar del mejor hardware. Un ingeniero bienintencionado decidió que el cuello de botella debía ser “sobrecarga del sistema de archivos”, así que cambiaron varios workloads a dispositivos de bloque crudos y subieron las profundidades de cola en la capa de aplicación al estilo fio.

Al principio, los dashboards mejoraron. El throughput aumentó, los jobs terminaron antes y el informe semanal contenía números que hicieron a todos sentirse competentes. Luego vino el efecto contrario: empezaron a aparecer picos de latencia en cola durante las horas punta, afectando servicios no relacionados que compartían el mismo hardware. Las unidades NVMe quedaron saturadas con colas profundas, y los servicios sensibles a latencia quedaron atrapados detrás de un muro de solicitudes pendientes.

La “optimización” mejoró throughput aumentando el encolamiento, pero dañó el sistema en general al incrementar la contención y la latencia de cola. Además redujo la observabilidad: al evitar la semántica del sistema de archivos perdieron algunos de los mecanismos y herramientas que usaban para diagnóstico.

La recuperación implicó limitar la concurrencia de I/O por workload, restaurar acceso basado en archivos donde tenía sentido operativo y tratar la profundidad de cola como un presupuesto de recurso en vez de una perilla de “hazlo más rápido”. Aún obtuvieron buen throughput, pero dejaron de sacrificar el resto de la flota para lograrlo.

Mini-story 3: The boring but correct practice that saved the day

Una plataforma de servicios financieros tenía una práctica poco glamorosa que en las revisiones de arquitectura parecía burocracia: cada ejecución de benchmark de almacenamiento llevaba un breve “registro de entorno”. Modelo/firmware del dispositivo, versión del kernel, scheduler, opciones de montaje, modo de caché, tamaño de prueba y si el sistema estaba aislado. Era papeleo, básicamente.

Un trimestre, apareció una regresión de latencia tras una actualización rutinaria. El equipo tenía buenas corazonadas pero ningún culpable claro. Algunos culpaban al kernel. Otros a la nueva canalización por lotes. Algunos querían revertir todo y darlo por zanjado.

Como las ejecuciones de benchmark eran reproducibles y estaban registradas, notaron rápidamente un pequeño pero significativo cambio: la selección del scheduler difería entre dos imágenes de host, y los nodos afectados tenían un patrón distinto de distribución de IRQ. El dispositivo en crudo no estaba “más lento”, pero la ruta CPU era más ruidosa y la latencia de cola peor con el mismo perfil de fio.

La solución fue aburrida: estandarizar la elección del scheduler para esa clase de dispositivos, aplicar una política consistente de afinidad de IRQ, volver a ejecutar la misma suite de fio como puerta y solo entonces continuar. Sin heroísmos ni llamadas nocturnas al proveedor. Lo que salvó el día no fue un truco de tuning; fue comparación disciplinada.

Listas de verificación / plan paso a paso

Step-by-step plan: a benchmark you can defend in a postmortem

  1. Escribe la intención de la carga. “Necesitamos latencia P99.9 predecible para lecturas aleatorias de 4k a concurrencia X” es útil. “Necesitamos discos rápidos” no lo es.
  2. Registra el entorno. Versión de Debian, kernel, versión de fio, modelo/firmware del dispositivo, sistema de archivos, opciones de montaje, scheduler.
  3. Elige el alcance de la prueba. Dispositivo crudo vs sistema de archivos. Si es sistema de archivos, registra espacio libre y riesgo de fragmentación.
  4. Controla la caché. Usa --direct=1 para caracterizar el dispositivo. Si usas búfer, justifícalo y demuestra el estado de la caché.
  5. Preacondiciona si SSD y el estado estable importa. No te saltes esto si escribes mucho en producción.
  6. Usa múltiples duraciones. 30s rápidos para sanidad, 10–30 minutos para comportamiento sostenido.
  7. Mide percentiles. Siempre. Si no te importa la latencia de cola, o estás de suerte o ejecutas un sistema batch que nadie vigila.
  8. Ejecuta con concurrencia realista. Empieza con lo que la app puede generar, luego explora más alto para ver margen y puntos de inflexión.
  9. Observa el sistema durante la ejecución. iostat -x, mpstat, térmicas del dispositivo.
  10. Repite e informa la varianza. Si los resultados varían mucho, no tienes un benchmark; tienes un misterio.
  11. Toma una decisión ligada a la métrica. Por ejemplo: “Aceptar si P99.9 < 2ms a iodepth=8, numjobs=4.”

Quick checklist: “Am I benchmarking the right thing?”

  • ¿Elegí explícitamente I/O directo vs en búfer?
  • ¿Mi tamaño de prueba es significativo respecto a la RAM?
  • ¿Registré ajustes de caché y semánticas de durabilidad?
  • ¿Recolecté percentiles de latencia?
  • ¿Casé tamaños de bloque y concurrencia con producción?
  • ¿Revisé térmicas y saturación de CPU?

FAQ

1) Should I always use --direct=1 on Debian 13?

Para caracterizar dispositivos y la mayoría de las afirmaciones sobre “rendimiento de almacenamiento”: sí. Usa I/O en búfer solo cuando modeles una aplicación que se beneficie intencionalmente de la caché de páginas, y entonces mide explícitamente el comportamiento de la caché.

2) Is io_uring always better than libaio?

No. io_uring suele ser más rápido y escalable, pero puede exponer cuellos de botella de CPU/IRQ y es sensible a peculiaridades del kernel/dispositivo. Usa ambos al diagnosticar; estandariza en uno para pruebas de regresión continuas para que los resultados sean comparables.

3) Why do my fio numbers look great but my database is slow?

Razones comunes: fio probó I/O secuencial mientras la base de datos hace aleatorio; fio no incluyó comportamiento de sync/flush; fio usó una profundidad de cola distinta a la de la base de datos; el journaling y la sobrecarga del sistema de archivos no se representaron; o tu base de datos está limitada por locks de CPU y no por I/O.

4) What block size should I test with?

Prueba varios tamaños, pero empieza con lo que usa tu app: 4k random reads es una línea base clásica; 16k/32k puede importar para algunas bases; 128k–1M para escaneos secuenciales y backups. Si eliges solo uno, accidentalmente optimizarás para ese único caso.

5) How long should fio runs be?

El tiempo suficiente para incluir el comportamiento que te interesa. Para ráfaga: 30–60 segundos puede bastar. Para comportamiento en estado estable de SSD y latencia de cola: minutos a decenas de minutos, tras preacondicionamiento si escribes mucho.

6) How do I avoid destroying data when testing raw devices?

Asume que fio arruinará tu día con gusto. Usa un dispositivo de prueba dedicado o un LV LVM desechable. Verifica tres veces --filename. Usa --rw=read para pruebas no destructivas y trata las pruebas de escritura como destructivas salvo que estés apuntando a un archivo de prueba en un sistema de archivos montado.

7) Why is “QD32” such a common default in examples?

Es un artefacto histórico de sistemas de almacenamiento donde colas profundas ayudaban a saturar controladores y discos. Aún puede ser útil para medir throughput pico, pero frecuentemente malrepresenta cargas sensibles a latencia y aplicaciones monohilo.

8) Do I need to drop caches between runs?

Si haces pruebas en búfer y tratas de simular cache fría, sí—con cuidado. En pruebas semejantes a producción, suele ser mejor usar I/O directo y evitar todo el problema de “gestión de caché” a menos que la caché sea parte del sistema que modelas.

9) My fio results differ between file and block tests. Which is “real”?

Ambos son reales; miden sistemas diferentes. Las pruebas de bloque miden dispositivo + capa de bloque del kernel; las de archivo incluyen asignación, metadatos y journaling del sistema de archivos. Elige la que responda a tu pregunta y no las promedies en una sola historia.

10) What’s the single most useful fio output field to look at?

Los percentiles de latencia (clat percentiles) bajo concurrencia realista. El throughput es negociable; la latencia de cola es donde vive el dolor visible por el usuario.

Conclusión: próximos pasos que realmente reducen el riesgo

Si tomas una lección operativa de esto: deja de discutir sobre “almacenamiento rápido” y empieza a definir “latencia aceptable bajo concurrencia realista”. Luego mide eso, controlando la caché y reportando percentiles.

Pasos concretos:

  1. Elige dos o tres perfiles de fio que coincidan con las formas de E/S en producción (lectura aleatoria 4k, mixto 70/30, patrón de escritura síncrona si aplica).
  2. Estandariza la captura del entorno (dispositivo, firmware, kernel, scheduler, sistema de archivos, opciones de montaje, directo/en búfer).
  3. Haz una puerta simple de aprobado/rehuso basada en latencia P99/P99.9, no en MB/s pico.
  4. Ejecuta preacondicionamiento + pruebas en estado estable para sistemas con SSD y cargas intensivas en escritura.
  5. Cuando los resultados te sorprendan, sigue la guía de diagnóstico rápido en vez de “tunear hasta que el gráfico se vea bien”.

fio no está mintiendo. Solo es obediente. Tu trabajo es hacerle las preguntas correctas—y negarte a aceptar respuestas halagadoras que no sobreviven al contacto con la producción.

← Anterior
Flash: la tecnología que estuvo en todas partes—hasta que desapareció
Siguiente →
Ubuntu 24.04: sudo lento — correcciones DNS/hostname que eliminan la demora (caso #6)

Deja un comentario