Todo está “bien” hasta que deja de estarlo. El p99 de la API dispara un circuito, tu base de datos se bloquea, los paneles parecen un sismógrafo, y alguien pronuncia la frase que escucharás en todas las empresas por siempre: “La app no cambió”.
Los picos de latencia de disco son el clásico chivo expiatorio y juego de conjeturas. Esto es el antídoto: un flujo de trabajo para Debian/Ubuntu que produce evidencia, no intuiciones—para que puedas demostrar que es el almacenamiento (o demostrar que no lo es), y luego arreglar lo correcto.
Guía de diagnóstico rápido
Si tienes 10 minutos, haz esto en orden. El truco es separar latencia de rendimiento, y dispositivo de bloque de sistema de archivos de aplicación. Un disco ocupado puede estar bien; un disco con pausas ocasionales de 2–20 segundos arruinará tu día.
1) Confirma el síntoma: ¿estamos bloqueados por I/O?
- Revisa a nivel sistema: ejecuta
vmstat 1y observawa(iowait) alto durante el pico. - Revisa por dispositivo: ejecuta
iostat -x 1y observaawaity%utilaumentando durante el pico. - Revisa la cola: si
avgqu-szcrece, estás acumulando solicitudes más rápido de lo que el dispositivo las completa.
2) Identifica la víctima: ¿qué proceso está bloqueado?
- Captura tareas bloqueadas:
ps -eo pid,stat,wchan:25,comm | awk '$2 ~ /D/'. - Correlación con la app: hilos de BD en estado
Dle dicen al SO “estoy esperando almacenamiento”.
3) Decide: ¿disco local, disco virtual o almacenamiento remoto?
- Mapea mount → dispositivo de bloque:
findmnt -no SOURCE,TARGET /your/mount. - Luego:
lsblk -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS,ROTA,MODELpara ver si estás en NVMe, SATA SSD, HDD, dm-crypt, LVM, MD RAID, multipath o un disco virtual de nube.
4) Si es un pico: traza, no promedies
- Usa
biosnoop(bcc) obpftracepara capturar outliers de latencia. - Si no puedes, usa
blktrace/blkparsey busca grandes intervalos entre dispatch y completion.
5) Valida con una reproducción controlada
- Ejecuta un perfil seguro de
fiocontra un archivo de prueba en el mismo sistema de archivos y comprueba si los picos p99/p999 coinciden con los síntomas de producción.
Qué significa realmente “pico de latencia de disco”
La latencia de disco es el tiempo entre “el kernel envía una solicitud de I/O de bloque” y “el kernel recibe la finalización”. Si ese tiempo se dispara, todo lo anterior se vuelve falso: el hilo de la app parece “lento”, los bloqueos parecen “contenciosos”, las colas se “construyen misteriosamente”, y la gente empieza a reescribir código en lugar de arreglar el cuello de botella.
Hay tres formas comunes de pico:
- Picos por encolamiento: la latencia sube porque estás saturando el dispositivo o backend. Síntomas:
%utilalto,avgqu-szalto,awaiten ascenso con IOPS estables. - Picos por pausa: la latencia salta de pocos ms a segundos con poco cambio en el rendimiento. Síntomas: paradas periódicas de varios segundos; a veces
%utilni siquiera parece pegado. Causas: fallos de firmware, garbage collection, limitación del backend remoto o commits del journal. - Picos por amplificación: pequeñas escrituras se convierten en muchas escrituras (journaling, copy-on-write, paridad RAID, cifrado). Síntomas: la app emite I/O “razonable”, el almacenamiento hace mucho más trabajo y la latencia se dispara bajo carga.
Una cita para pegar en un post-it:
“La esperanza no es una estrategia.” — General Gordon R. Sullivan
También: tu app puede ser inocente y aun así ser el detonante. Un cambio de carga sin un despliegue (nuevo cliente, consulta de forma diferente, nueva construcción de índice, compactación en segundo plano) puede empujar el almacenamiento al precipicio. Tu trabajo es demostrar que el precipicio existe.
Broma #1: La latencia de disco es como una reunión que “solo tomará cinco minutos.” No lo hará.
Hechos y contexto interesantes (porque la historia se repite)
- “iowait” no es “el disco es lento”. Es tiempo de CPU pasado en inactividad mientras el sistema tiene I/O pendiente. Una app que consume mucha CPU puede tener iowait bajo y una latencia de almacenamiento terrible.
- El elevator de Linux solía ser una característica destacada. Planificadores antiguos como anticipatory y CFQ se diseñaron para discos giratorios y respuesta interactiva; los SSDs y NVMe cambiaron el equilibrio hacia mq-deadline/none.
- NCQ y colas profundas cambiaron los modos de fallo. SATA NCQ permitió que los dispositivos reordenaran solicitudes; también hizo más visible que “un comando malo puede detener la cola” cuando el firmware falla.
- Los SSDs pueden pausar para “limpiar la casa”. Garbage collection y wear leveling pueden causar picos periódicos de latencia, especialmente cuando la unidad está casi llena o carece de sobreprovisionamiento.
- El journaling intercambió pérdida de datos por previsibilidad de latencia. ext3/ext4 con journaling hizo los fallos menos dramáticos, pero el comportamiento de commits puede crear ráfagas de escritura periódicas y paradas por sincronización.
- Los barriers de escritura se volvieron predeterminados por una razón. Barriers (flush/FUA) evitan reordenamientos que pueden corromper metadata tras una pérdida de energía; también pueden exponer una ruta de flush de caché lenta.
- Los discos virtuales son fronteras políticas. En la nube, tu “disco” es una porción de un backend compartido; la limitación por throttling y créditos de ráfaga pueden hacer que aparezcan picos de latencia de la nada.
- RAID oculta problemas de rendimiento más que de latencia. Puedes añadir discos y obtener más MB/s, pero las escrituras aleatorias pequeñas en RAID de paridad siguen pagando un impuesto.
Tareas prácticas: comandos, qué significa la salida y qué hacer después
Esta sección es intencionalmente práctica. Quieres artefactos repetibles: logs, marcas de tiempo y una narrativa que sobreviva a la próxima reunión.
Task 1: Establece la verdad base (kernel, dispositivo, virtualización)
cr0x@server:~$ uname -a
Linux db-01 6.5.0-28-generic #29~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC x86_64 GNU/Linux
cr0x@server:~$ systemd-detect-virt
kvm
cr0x@server:~$ lsb_release -ds
Ubuntu 22.04.4 LTS
Significado: La versión del kernel y la virtualización influyen mucho en el comportamiento de almacenamiento (multi-queue, valores por defecto del scheduler, virtio). Si es una VM, también debes pensar en vecinos ruidosos y limitación del backend.
Decisión: Si está virtualizado, planea recolectar evidencia que resista la conversación “es problema del guest”: latencia por dispositivo, profundidad de cola, señales de throttling y correlación temporal.
Task 2: Mapea mounts a dispositivos de bloque (no adivines)
cr0x@server:~$ findmnt -no SOURCE,TARGET,FSTYPE,OPTIONS /var/lib/postgresql
/dev/mapper/vg0-pgdata /var/lib/postgresql ext4 rw,relatime,discard
cr0x@server:~$ lsblk -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS,ROTA,MODEL
NAME TYPE SIZE FSTYPE MOUNTPOINTS ROTA MODEL
vda disk 500G 1 QEMU HARDDISK
└─vda2 part 499.5G LVM2_member 1
├─vg0-root lvm 50G ext4 / 1
└─vg0-pgdata lvm 449G ext4 /var/lib/postgresql 1
Significado: La app está en LVM sobre vda. El flag rotacional muestra “1” (HDD) incluso en entornos virtuales; trátalo como “no NVMe-rápido”. También observa discard—eso puede importar.
Decisión: Tu cuello de botella podría estar debajo de LVM (virtio, almacenamiento del host, bloques en red). Mantén el mapeo: las pilas de device-mapper añaden colas y complejidad.
Task 3: Observa la presión del sistema, no solo promedios
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 612304 64216 931224 0 0 120 410 410 880 12 4 82 2 0
1 0 0 610992 64216 931260 0 0 180 512 398 860 10 4 84 2 0
1 5 0 610120 64216 930800 0 0 140 2100 520 1200 6 3 60 31 0
0 6 0 609880 64216 930744 0 0 90 1800 540 1300 5 3 58 34 0
1 0 0 610400 64216 931100 0 0 110 600 420 900 9 4 84 3 0
Significado: Durante el pico, b (procesos bloqueados) sube y wa salta a 31–34%. Esa es una firma real de bloqueo.
Decisión: Si los procesos bloqueados aumentan durante picos de latencia, pasa a métricas por dispositivo. Si wa es bajo pero la latencia p99 es alta, la app podría estar limitada por CPU o esperando bloqueos, no por I/O.
Task 4: Obtén latencia por dispositivo y señales de cola
cr0x@server:~$ iostat -x 1 5
Linux 6.5.0-28-generic (db-01) 12/30/2025 _x86_64_ (8 CPU)
Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %rrqm %wrqm r_await w_await aqu-sz rareq-sz wareq-sz svctm %util
vda 8.00 95.00 512.0 8240.0 0.00 12.00 0.00 11.21 3.10 18.40 1.90 64.0 86.7 1.20 12.40
vda 7.00 110.00 448.0 9100.0 0.00 10.00 0.00 8.33 3.40 120.50 14.20 64.0 82.7 1.50 17.50
vda 6.00 105.00 384.0 9000.0 0.00 11.00 0.00 9.48 3.80 240.10 28.30 64.0 85.7 1.60 18.00
vda 9.00 98.00 576.0 8600.0 0.00 14.00 0.00 12.50 3.20 22.00 2.30 64.0 87.8 1.30 13.50
vda 8.00 92.00 512.0 8100.0 0.00 12.00 0.00 11.54 3.10 19.30 2.00 64.0 88.0 1.20 12.60
Significado: Las escrituras son el problema (w_await salta a 120–240ms) mientras que el %util no está extremo. Eso es clásico “el backend se volvió lento” o “ruta de flush/commit” en lugar de “dispositivo saturado”. aqu-sz se incrementa durante picos: hay encolamiento.
Decisión: Cuando await sube pero la utilización no llega al máximo, sospecha pausas: flushes de caché, thin provisioning, throttling remoto o contención en el host. Pasa al trazado e investigación de flush/cola.
Task 5: Confirma qué procesos están en sueño ininterrumpible (estado D)
cr0x@server:~$ ps -eo pid,stat,wchan:25,comm | awk '$2 ~ /D/'
18423 D io_schedule postgres
18431 D io_schedule postgres
21102 D ext4_writepages postgres
Significado: Workers de Postgres están bloqueados en rutas de espera del kernel relacionadas con I/O. Eso no es “SQL lento” por sí solo; es “la finalización del almacenamiento llega tarde”.
Decisión: Si hilos de la app están en estado D durante ventanas de pico, prioriza evidencia del bloque y causas del lado del almacenamiento. Si están ejecutables (R) pero lentos, enfócate en CPU, bloqueos, GC o red.
Task 6: Lee la información de presión de I/O (PSI) para demostrar contención
cr0x@server:~$ cat /proc/pressure/io
some avg10=0.28 avg60=0.22 avg300=0.15 total=184329210
full avg10=0.07 avg60=0.05 avg300=0.03 total=40210299
Significado: PSI te dice con qué frecuencia las tareas se retrasan esperando I/O. full indica tiempos en que el sistema no tenía tareas ejecutables porque todos esperaban I/O. Eso es una señal fuerte de “el almacenamiento está lo que limita la máquina”.
Decisión: Si full de PSI sube durante picos, trátalo como infraestructura, no como app. Si PSI está quieto, tu latencia podría estar dentro de la app (bloqueos) o dentro del caché del sistema de archivos (fallos de página) en lugar de I/O de bloque.
Task 7: Inspecciona el scheduler del dispositivo y ajustes de cola
cr0x@server:~$ cat /sys/block/vda/queue/scheduler
[mq-deadline] none
cr0x@server:~$ cat /sys/block/vda/queue/nr_requests
256
cr0x@server:~$ cat /sys/block/vda/queue/read_ahead_kb
128
Significado: La elección del scheduler importa para la latencia. mq-deadline suele ser un buen valor por defecto para dispositivos que necesitan equidad. La profundidad de cola (nr_requests) influye en el comportamiento de ráfagas y la latencia de cola.
Decisión: No “tunées” a ciegas. Si ves cola larga de lat extremo, puede que necesites reducir el encolamiento para mantener la latencia acotada, especialmente en backends compartidos. Planea pruebas controladas.
Task 8: Comprueba las opciones de montaje del sistema de archivos que fuerzan comportamiento síncrono
cr0x@server:~$ findmnt -no TARGET,FSTYPE,OPTIONS /var/lib/postgresql
/var/lib/postgresql ext4 rw,relatime,discard
cr0x@server:~$ tune2fs -l /dev/mapper/vg0-pgdata | egrep 'Filesystem features|Journal features'
Filesystem features: has_journal ext_attr resize_inode dir_index filetype extent 64bit flex_bg sparse_super large_file huge_file dir_nlink extra_isize metadata_csum
Journal features: journal_incompat_revoke journal_64bit journal_checksum_v3
Significado: discard puede causar picos de latencia dependiendo del backend. La recomendación moderna suele ser fstrim periódico en lugar de discard en línea para cargas sensibles a latencia.
Decisión: Si ves picos durante deletes/vacuum/compaction, intenta deshabilitar discard y usar fstrim programado. Valida con control de cambios y medición.
Task 9: Observa flushes y comportamiento de writeback (throttling por dirty)
cr0x@server:~$ sysctl vm.dirty_background_ratio vm.dirty_ratio vm.dirty_expire_centisecs vm.dirty_writeback_centisecs
vm.dirty_background_ratio = 10
vm.dirty_ratio = 20
vm.dirty_expire_centisecs = 3000
vm.dirty_writeback_centisecs = 500
cr0x@server:~$ grep -E 'Dirty:|Writeback:' /proc/meminfo
Dirty: 182340 kB
Writeback: 2048 kB
Significado: Los umbrales de páginas sucias dictan cuándo el kernel fuerza writeback. Cuando alcanzas dirty_ratio, las escrituras de la app pueden ser fuertemente limitadas, lo que parece picos aleatorios de latencia.
Decisión: Si los picos se correlacionan con ráfagas de memoria sucia y writeback, ajusta los parámetros dirty con cuidado o reduce la amplificación de escritura (batching de la app, ajustes de BD). Sé conservador; estos controles pueden empeorar las cosas.
Task 10: Captura outliers de latencia de almacenamiento con BPF (biosnoop de bcc)
cr0x@server:~$ sudo biosnoop -Q -d vda
TIME(s) COMM PID DISK T SECTOR BYTES LAT(ms)
12.4321 postgres 18423 vda W 9132456 16384 14.92
12.4398 postgres 18431 vda W 9132488 16384 18.77
12.9802 postgres 18423 vda W 9132520 16384 942.51
13.0054 postgres 18431 vda W 9132552 16384 1103.44
Significado: Esto es la prueba definitiva: solicitudes reales de I/O con latencia medida, atribuidas a un proceso. Esos writes de 900–1100ms explican los timeouts p99 de la app.
Decisión: Si BPF muestra outliers en el dispositivo de bloque, puedes dejar de discutir sobre la app. Ahora averigua por qué: flushes, throttling, pausas del backend, encolamiento o errores del dispositivo.
Task 11: Traza la ruta del bloque con blktrace para tiempos más profundos
cr0x@server:~$ sudo blktrace -d /dev/vda -o - | blkparse -i -
8,0 1 1 0.000000000 18423 Q WS 9132520 + 32 [postgres]
8,0 1 2 0.000310215 18423 G WS 9132520 + 32 [postgres]
8,0 1 3 0.000482906 18423 I WS 9132520 + 32 [postgres]
8,0 1 4 0.000650102 18423 D WS 9132520 + 32 [postgres]
8,0 1 5 0.942912433 18423 C WS 9132520 + 32 [0]
Significado: Esto muestra el ciclo de vida de la solicitud: queued (Q), dispatched (D), completed (C). Aquí, la finalización ocurre ~0.942s después del dispatch. Eso es tiempo del dispositivo/backend, no de tu parser SQL.
Decisión: Si el tiempo está mayormente entre D y C, enfócate en el dispositivo/backend. Si el retraso está entre Q y D, estás encolando en el SO (scheduler/profundidad de cola), a menudo por saturación o pilas device-mapper apiladas.
Task 12: Comprueba problemas reportados por el kernel (los logs aburridos importan)
cr0x@server:~$ sudo dmesg -T | egrep -i 'blk|I/O error|timeout|reset|nvme|scsi|ext4|xfs' | tail -n 8
[Mon Dec 30 10:12:41 2025] blk_update_request: I/O error, dev vda, sector 9132520 op 0x1:(WRITE) flags 0x0 phys_seg 2 prio class 0
[Mon Dec 30 10:12:41 2025] Buffer I/O error on dev dm-1, logical block 1141568, lost async page write
[Mon Dec 30 10:12:41 2025] EXT4-fs warning (device dm-1): ext4_end_bio:345: I/O error 10 writing to inode 262411 starting block 1141568)
Significado: Si tienes errores I/O o resets, la latencia deja de ser la historia principal. Tienes riesgo de integridad. Los picos pueden ser reintentos, remapeos o timeouts del backend.
Decisión: Escala inmediatamente: equipo de almacenamiento/proveedor cloud/propietario del hipervisor. Empieza a planear failover y chequeos de integridad de datos, no micro-optimizaciones.
Task 13: Verifica el comportamiento TRIM (discard vs trim programado)
cr0x@server:~$ systemctl status fstrim.timer
● fstrim.timer - Discard unused blocks once a week
Loaded: loaded (/lib/systemd/system/fstrim.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Mon 2025-12-30 09:00:01 UTC; 1h 12min ago
Trigger: Mon 2026-01-06 00:00:00 UTC; 6 days left
cr0x@server:~$ sudo fstrim -v /var/lib/postgresql
/var/lib/postgresql: 94.3 GiB (101251604480 bytes) trimmed
Significado: Si puedes programar trim, a menudo puedes eliminar discard en línea. Eso puede reducir picos de latencia durante borrados intensos.
Decisión: Prefiere fstrim.timer en muchos backends. Si estás en SAN thin-provisioned o ciertos discos cloud, valida con tu proveedor/equipo de almacenamiento.
Task 14: Reproduce con fio y mira la latencia de cola, no solo IOPS
cr0x@server:~$ fio --name=latcheck --filename=/var/lib/postgresql/fio.test --size=2G --direct=1 --ioengine=libaio --rw=randwrite --bs=16k --iodepth=16 --numjobs=1 --time_based --runtime=30 --group_reporting --output-format=normal
latcheck: (g=0): rw=randwrite, bs=(R) 16.0KiB-16.0KiB, (W) 16.0KiB-16.0KiB, ioengine=libaio, iodepth=16
fio-3.33
latcheck: Running 1 job
write: IOPS=1420, BW=22.2MiB/s (23.3MB/s)(666MiB/30001msec)
slat (usec): min=6, max=421, avg=14.52, stdev=9.30
clat (msec): min=0, max=1840, avg=10.62, stdev=41.10
lat (msec): min=0, max=1840, avg=10.65, stdev=41.11
clat percentiles (msec):
| 50.00th=[ 1], 90.00th=[ 3], 99.00th=[ 120], 99.90th=[ 820], 99.99th=[1700]
Significado: La mediana está bien; la cola es horrible. Eso es exactamente cómo se siente producción: mayormente bien, ocasionalmente catastrófico. Los percentiles de cola confirman el comportamiento incluso en una prueba controlada.
Decisión: Si fio reproduce los picos de cola, no es tu app. Ahora puedes probar arreglos (scheduler, discard, profundidad de cola, ajustes dirty) y medir la mejora.
Construir el caso: almacenamiento vs app (cómo probarlo sin empezar una guerra)
“Probar que es almacenamiento” significa producir una cadena de evidencia desde la latencia visible por el usuario hasta el tiempo de finalización I/O a nivel kernel. Quieres correlación, atribución y un mecanismo plausible.
Escalera de evidencia (úsala como en una corte)
- Síntoma de usuario: p95/p99 con picos de latencia, timeouts, tormentas de reintentos, crecimiento de colas.
- Síntoma del host: tareas bloqueadas, PSI I/O, iowait elevado durante ventanas del incidente.
- Síntoma del dispositivo:
iostat -xmuestra picos deawaity crecimiento de colas; a veces sin saturación. - Prueba por I/O: herramientas BPF o blktrace muestran I/Os individuales que tardan 200ms–segundos, atribuidos al proceso y dispositivo.
- Mecanismo: algo que explique por qué: tormenta de flush, contención de metadatos en thin pool, throttling cloud, discard, penalidad RAID, GC de firmware, failover multipath, etc.
Qué no hacer
- No uses “CPU iowait es alto” como único argumento. Es sugestivo, no concluyente.
- No te apoyes en
svctmcomo verdad absoluta. En kernels modernos y dispositivos apilados, suele ser engañoso. - No promedies para escapar de la latencia de cola. Los picos viven en p99/p999, no en la media.
Cómo los problemas de la app se hacen pasar por almacenamiento (y cómo separarlos)
A veces la app es culpable y el almacenamiento es el testigo. Aquí los impostores comunes:
- Contención de locks: hilos de la app bloqueados en mutex parecen “todo lento”, pero el kernel muestra tareas ejecutables, no estado D de I/O.
- Pausas de GC o compactación: la app se para pero las métricas de disco se mantienen estables; la CPU puede dispararse o los patrones de pausa son periódicos.
- Dependencia de red: llamadas remotas causan latencia; el disco está bien; los procesos bloqueados no están por I/O.
- Fallos del caché del sistema de archivos: fallos de página mayores pueden parecer I/O, pero verás un patrón en
vmstaty contadores perf; sigue siendo almacenamiento, pero en otra capa.
Broma #2: El equipo de app dirá que es almacenamiento; el de almacenamiento dirá que es la app. Felicitaciones, ahora brindas diplomacia como servicio.
Tres micro-historias corporativas (cómo falla esto en la vida real)
Micro-historia 1: El incidente causado por una suposición equivocada
La empresa tenía un servicio de checkout en VMs Ubuntu, respaldado por un volumen gestionado. Una integración con un partner nueva se lanzó un lunes. Sin despliegues, sin cambios de esquema, sin flags obvios. A mediodía, los p99 latencias se dispararon a segundos y el chat de on-call hizo lo que hacen los chats de on-call: producir teorías más rápido que datos.
La suposición prevalente: “Si fuera almacenamiento, veríamos 100% de utilización del disco.” Los gráficos mostraban %util en torno a las decenas. Alguien declaró al almacenamiento inocente y culpó la API del partner. Ingenieros empezaron a añadir caching, ajustar timeouts y lógica de reintentos—aumentando la carga en la base de datos.
Más tarde esa tarde, un SRE ejecutó biosnoop y capturó latencias de escritura periódicas de 800–1500ms en el volumen. La tasa de solicitudes no era alta; el backend simplemente estaba ocasionalmente lento. El concepto que faltaba era que los picos de latencia pueden ocurrir sin saturación local cuando estás en un backend compartido o con throttling.
La solución no fue heroica: la carga cambió hacia más escrituras pequeñas. Subieron la clase del volumen a una con mejor latencia base y eliminaron una opción de montaje que provocaba discards síncronos en ráfagas de borrado. La API del partner estaba bien. La suposición original no lo estaba.
Micro-historia 2: La optimización que salió mal
Un equipo quería acelerar jobs nocturnos. Alguien notó los ajustes de páginas sucias del kernel y decidió “dejar que Linux haga más buffering”, incrementando vm.dirty_ratio y vm.dirty_background_ratio. El job batch fue más rápido la primera hora. Slack celebró. Se escribió una RFC retroactiva. Ya sabes a dónde va esto.
En producción, el tráfico diurno también escribe. Con umbrales sucios más altos, el kernel acumuló más datos sucios y luego los volcó en ráfagas más grandes. El almacenamiento no estaba saturado en promedio, pero la ráfaga creó paradas de varios segundos cuando el sistema alcanzó el límite sucio y limitó a los escritores.
La base de datos no solo se ralentizó; empezó a generar timeouts de cliente, que desencadenaron reintentos y amplificaron la presión de escritura. El equipo de app vio timeouts y culpó planes de consulta. Infra vio IOPS promedio aceptables y se encogió de hombros. La latencia de cola era la única métrica que importaba, y estaba en llamas.
El rollback restauró la estabilidad inmediatamente. La solución duradera fue pruebas de carga disciplinadas con seguimiento de p99/p999, y una ventana de batch separada con límites de velocidad. La “optimización” fue real para el rendimiento y desastrosa para la latencia. Ambas cosas pueden ser verdad.
Micro-historia 3: La práctica aburrida pero correcta que salvó el día
En otro lugar, el equipo de plataforma de almacenamiento tenía una costumbre que parecía poco cool pero funcionaba: cada host emitía un pequeño conjunto de métricas SLO de almacenamiento—await del dispositivo, full de PSI I/O y un histograma de latencia de I/O de bloque desde un programa eBPF muestreado durante picos. También mantenían en inventario opciones de montaje y pilas de device mapper.
Un jueves, varios servicios empezaron a mostrar picos p99 sincronizados entre apps no relacionadas. Porque la telemetría era consistente, el on-call no empezó con “qué cambió en la app”. Empezaron con: “¿qué es común en estos hosts?” El histograma mostró escrituras de cola larga en volúmenes adjuntos a un cluster de hypervisores.
Hicieron snapshots de procesos en estado D y confirmaron múltiples procesos bloqueados en io_schedule en diferentes servicios. Eso cambió la conversación de “bug de app” a “backend de almacenamiento compartido”. El propietario del hypervisor encontró un evento de mantenimiento que había puesto una pool de almacenamiento en estado degradado. Ninguna VM estaba “saturada”. El backend lo estaba.
El resultado fue anticlimático: migraron cargas fuera de los hosts afectados y la pool se reparó. Lo que salvó fue aburrido: medición y hábito de correlacionar tiempo, dispositivo y procesos. En ops, lo aburrido es una característica.
Soluciones que realmente mueven la aguja (ordenadas por frecuencia de éxito)
Una vez que hayas probado que la latencia está en la ruta de almacenamiento, tus soluciones deben apuntar al mecanismo observado. No lances sysctls al azar y esperes. La latencia de cola castiga la superstición.
1) Arregla la clase/límites del backend (cloud y entornos virtualizados)
Si estás en un volumen cloud o SAN compartido, puedes estar alcanzando throttling, agotamiento de créditos de ráfaga o contención por vecino ruidoso. El patrón de evidencia es: await que se dispara sin saturación local, picos de cola en fio y a menudo periodicidad repetible.
- Acción: cambia a una clase de volumen con mejor latencia base / IOPS provisionadas; reduce la varianza pagando por ello.
- Acción: distribuye datos calientes en varios volúmenes (striping a nivel app/BD o LVM) si procede.
- Evita: “solo añade reintentos.” Los reintentos convierten pequeños incidentes en outages.
2) Elimina discard en línea para sistemas sensibles a latencia (usa fstrim)
El discard en línea puede convertir deletes en operaciones trim síncronas. En algunos backends está bien; en otros es una mina de latencia.
- Acción: elimina
discardde fstab para el volumen de datos, remonta y confía enfstrim.timer. - Valida: ejecuta fio y observa mejora en la latencia de cola.
3) Acota el encolamiento para reducir la latencia de cola
Colas profundas mejoran throughput hasta que destrozan p99. Para backends compartidos, profundidades de cola enormes pueden convertir una corta parada en una larga al apilar trabajo detrás de ella.
- Acción: prueba scheduler
mq-deadlinevsnonepara NVMe/virtio. Elige el que reduzca la latencia de cola bajo tu carga, no el que gane microbenchmarks. - Acción: considera reducir la profundidad de cola (específico del proveedor en VMs; en Linux, la cola del dispositivo y la iodepth de la aplicación importan).
4) Elimina la amplificación de escrituras
Si tu app emite escrituras pequeñas y aleatorias, cada capa puede multiplicarlas en más I/O del que piensas.
- Acción: para bases de datos, alinea el comportamiento de checkpoints/flush con las características de almacenamiento; evita ajustes que creen enormes tormentas periódicas de flush.
- Acción: verifica si estás en RAID de paridad para cargas con muchas escrituras pequeñas; considera mirror/striped mirrors para escrituras sensibles a latencia.
- Acción: evita apilar LVM + dm-crypt + MD RAID a menos que lo necesites; cada capa añade colas y modos de fallo.
5) Ajuste de páginas sucias (solo con medición)
El tuning de writeback del kernel puede reducir ráfagas, pero es fácil empeorar. La postura segura: cambios pequeños, probados bajo carga, observando latencia p99 de escritura y SLOs de la app.
- Acción: si ves paradas periódicas al límite sucio, baja ligeramente
vm.dirty_ratiopara forzar un writeback más suave. - Acción: considera usar
vm.dirty_background_bytesyvm.dirty_bytesen lugar de ratios en hosts con memoria variable.
6) Arregla rutas de error y problemas de firmware
Si dmesg muestra resets/timeouts/errores, trátalo como incidente de fiabilidad. Los picos muchas veces son reintentos y resets de controlador disfrazados.
- Acción: actualiza firmware de las unidades / drivers de almacenamiento del hipervisor cuando proceda.
- Acción: reemplaza dispositivos fallando; deja de intentar tunear la física.
Errores comunes: síntoma → causa raíz → arreglo
1) Picos p99 de la app, pero %util del disco es bajo
Síntoma: Usuarios ven timeouts; iostat muestra %util bajo pero await con picos.
Causa raíz: pausa del backend o throttling (volumen cloud, contención SAN, ruta de flush de caché) más que saturación sostenida.
Arreglo: captura latencia por I/O con BPF/blktrace; migra a clase de volumen con mejor latencia base o reduce disparadores de flush/trim; ajusta colas para latencia de cola.
2) Iowait alto lleva a “culpar al disco”, pero await está bien
Síntoma: vmstat muestra wa alto, pero iostat -x muestra await bajo.
Causa raíz: el sistema espera otra cosa (picos en servidor NFS, filesystem en red, I/O de swap, o una app que causa lecturas bloqueadas por fallos de página en otro dispositivo).
Arreglo: mapea mounts a dispositivos; comprueba estadísticas NFS si aplica; identifica procesos bloqueados y sus canales de espera; traza el dispositivo correcto.
3) Picos periódicos cada pocos segundos/minutos
Síntoma: la cadencia del pico parece un metrónomo.
Causa raíz: commits de journal, checkpoints, timers de writeback, trim periódico o housekeeping del backend de almacenamiento.
Arreglo: correlaciona con logs del sistema de archivos/BD; elimina discard en línea; ajusta comportamiento de checkpoint/commit; mide con fio y BPF.
4) Picos durante cargas con muchos deletes
Síntoma: vacuum/compaction/eliminaciones coinciden con paradas I/O.
Causa raíz: discard síncrono, presión de metadatos en thin provisioning o presión de GC en SSD por ráfagas de invalidación.
Arreglo: usa trim programado; garantiza espacio libre/overprovisioning adecuado; considera una clase de SSD o configuración de backend mejor.
5) “Hicimos cifrado y ahora está lento”
Síntoma: mayor latencia y menor throughput tras habilitar dm-crypt.
Causa raíz: sobrecarga CPU, tamaños efectivos de solicitud menores, pérdida de offloads del dispositivo o interacciones de colas en la pila device-mapper.
Arreglo: confirma con perf y uso de CPU; asegúrate de que AES-NI esté disponible; considera optimizar tamaños de I/O e iodepth; mantén pilas mínimas.
6) RAID “funciona” hasta que llegan escrituras aleatorias pequeñas
Síntoma: lecturas bien; escrituras con latencia de cola terrible bajo carga.
Causa raíz: penalidad de escritura en RAID por paridad y ciclos read-modify-write; comportamiento de flush de caché.
Arreglo: usa mirrors para cargas de escritura sensibles a latencia; asegura que la caché de escritura sea segura (BBU/PLP) y que los flushes sean razonables; incrementa alineamiento de stripe cuando aplique.
Listas de verificación / plan paso a paso
Paso a paso: de la alerta a la causa raíz de forma controlada
- Captura timestamps. Anota inicio/fin de las ventanas de pico. La correlación muere sin tiempo.
- Confirma impacto en el host. Registra
vmstat 1y salida PSI I/O durante el pico. - Recolecta estadísticas por dispositivo. Ejecuta
iostat -x 1al menos 60 segundos abarcando un pico. - Identifica procesos bloqueados. Haz un snapshot de tareas en estado D y sus canales de espera.
- Mapea la ruta de datos. Mount → sistema de archivos → dm-crypt/LVM/MD → disco físico/virtual.
- Traza outliers. Usa
biosnoopoblktracepara capturar un puñado de I/Os de peor latencia. - Revisa logs por riesgos de integridad. Escanea dmesg en busca de errores/timeouts/resets de I/O.
- Reproduce de forma segura. Ejecuta fio en un archivo de prueba para validar picos de cola fuera de la app.
- Formula una hipótesis. “Los picos son causados por X porque la evidencia Y muestra latencia entre D y C y se correlaciona con Z.”
- Aplica un cambio. Uno. No cinco. Mide de nuevo con las mismas herramientas.
- Fija la monitorización. Mantén PSI I/O, await de iostat y una sonda de latencia de cola como señales estándar.
Checklist operativo: qué adjuntar al ticket del incidente
- Salida iostat abarcando el pico (texto crudo).
- Snapshot PSI I/O y salida de
vmstat. - Lista de procesos en estado D con canales de espera.
- Un artefacto de trazado (líneas de biosnoop o extracto de blktrace) mostrando latencias outlier.
- Extracto dmesg para advertencias/errores relacionados con almacenamiento.
- Topología de almacenamiento: salida de lsblk mostrando pilas (dm-crypt/LVM/MD/multipath).
- Nota de carga: qué estaba haciendo la app (checkpoint, vacuum, compaction, job batch).
Preguntas frecuentes
1) ¿Es suficiente un iowait alto para probar que el almacenamiento es el cuello de botella?
No. Es una pista. Pruébalo con latencia por dispositivo (iostat -x) y trazado por I/O (BPF o blktrace). iowait puede estar bajo durante picos cortos.
2) ¿Por qué la latencia se dispara cuando %util no está cerca de 100%?
Porque la utilización es un promedio y a menudo local al guest. Pausas del backend, throttling, flushes de caché o contención remota pueden crear tiempos de finalización altos sin saturación local.
3) ¿Cuál es la manera más rápida de atribuir I/O lento a un proceso?
Usa biosnoop (bcc) u otra herramienta eBPF similar. Registra la latencia I/O y muestra qué proceso la emitió. Eso termina discusiones rápidamente.
4) ¿Debería cambiar el scheduler a “none” para SSD/NVMe?
A veces sí, pero mide. “none” puede reducir overhead, pero también permitir injusticias y peor latencia de cola en backends compartidos. Prueba con fio y carga similar a producción.
5) ¿Es realmente tan malo el discard en línea?
Puede serlo. En algunos dispositivos es barato; en otros desencadena trabajo costoso en momentos terribles. Si ves picos durante deletes, prueba trim programado en su lugar.
6) Mi prueba fio muestra p99 terrible, pero la app está “bien” la mayor parte del día. ¿Ahora qué?
Tienes un problema de latencia de cola que saldrá a la luz bajo la concurrencia o actividad de fondo equivocada. Arréglalo ahora, antes de un outage que solo ocurre los martes.
7) ¿Puede la elección del sistema de archivos (ext4 vs xfs) arreglar picos de latencia?
A veces, pero raramente es la palanca primaria. La mayoría de los picos vienen de la varianza del backend, amplificación de escritura, encolamiento o comportamiento de flush. Cambiar sistema de archivos es disruptivo; agota soluciones más simples primero.
8) ¿Cómo distingo encolamiento en el SO de una finalización lenta del dispositivo?
Usa el timing del ciclo de vida en blktrace. Si el retraso está entre Q y D, estás encolando antes del dispatch. Si el retraso está entre D y C, el dispositivo/backend es lento.
9) ¿Por qué los picos empeoran cuando añadimos reintentos?
Los reintentos añaden carga justo cuando el sistema está más débil. Incrementan la concurrencia, profundizan colas y extienden el pico. Prefiere backoff, jitter y circuit breakers—y arregla la causa raíz.
Conclusión: siguientes pasos que no te harán perder una semana
Cuando aparecen picos de latencia de disco, el peligro no es solo rendimiento. Es el diagnóstico erróneo. Gente reescribe código, “optimiza” la ruta equivocada y publica cambios que hacen el incidente más grande y difícil de entender.
Haz esto a continuación:
- Instrumenta: mantén PSI I/O y latencia de dispositivo estilo
iostat -xen tus dashboards estándar. - Durante el próximo pico, captura un artefacto de trazado (BPF o blktrace) que muestre un I/O outlier y su latencia.
- Elimina multiplicadores obvios de latencia (discard en línea, encolamiento patológico) y vuelve a probar con fio usando percentiles.
- Si estás en almacenamiento compartido/virtualizado y puedes reproducir picos de cola, deja de negociar con la física: mejora la clase del backend o rediseña el layout.
Una vez que puedas señalar un I/O específico que tardó 1.1 segundos y nombrar el proceso que lo emitió, la conversación cambia. Ese es el objetivo. La evidencia vence a las opiniones, y además te hace menos popular en reuniones—lo cual, honestamente, a veces es una ventaja.