“El disco está lento” es el informe de incidente menos útil y el más común. Se manifiesta como timeouts en tu aplicación, copias de seguridad que de repente tardan toda la noche, o una base de datos que empieza a comportarse como si estuviera haciendo trabajo filosófico profundo entre escrituras.
Ubuntu 24.04 incluye un kernel Linux moderno con bloque IO multi-cola y bastantes valores predeterminados sensatos. Aun así, un planificador equivocado, una profundidad de cola que no coincide o un ajuste “útil” pueden convertir un NVMe rápido en un aspersor de latencias. La solución rara vez es mística. Normalmente es medición, un par de ajustes deliberados y el hábito de demostrar que el cambio realmente ayudó.
Guía rápida de diagnóstico
Cuando alguien dice “el disco está lento”, no empiezas cambiando parámetros. Empiezas por saber si tienes un problema de latencia, un techo de throughput, un problema de saturación o una simple falla de dispositivo. Aquí está el orden que detecta la mayoría de problemas con el menor drama.
1) Primero: identifica qué tipo de “lento” es
- Pico de latencia (p99/p999 de lecturas o fsyncs se disparan): a menudo cola, desajuste de planificador, vaciados de caché, firmware/GC del dispositivo o almacenamiento backend.
- Techo de throughput (MB/s atascados bajos): a menudo velocidad de enlace, problemas de líneas PCIe, limitaciones de RAID/MD, límites de volúmenes en la nube o patrón IO incorrecto.
- Saturación (alta utilización, colas largas): a menudo profundidad de cola demasiado baja/alta, demasiados escritores concurrentes o un “vecino ruidoso”.
- Bloqueos/timeouts: pueden ser errores de dispositivo, failovers de multipath, reinicios de controlador o dolores del sistema de archivos/journal.
2) Segundo: revisa errores y reinicios antes de sintonizar
Si el kernel está registrando resets de NVMe o timeouts SCSI, tu “problema de rendimiento” es un problema de disponibilidad con disfraz de rendimiento.
3) Tercero: mide encolamiento y latencia en la capa de bloque
Usa iostat y pidstat para ver si estás acumulando colas (aqu-sz), sufriendo latencia (await) o simplemente haciendo mucho IO. Luego mapea eso a la configuración del planificador y la profundidad de cola.
4) Cuarto: valida el planificador y la profundidad de cola según el tipo de dispositivo
NVMe, SATA SSD, HDD y volúmenes de bloque en la nube son animales distintos. Si los tratas igual, te castigarán de formas distintas.
5) Quinto: cambia una cosa, ejecuta la misma prueba, compara percentiles
Si no puedes reproducir el problema, no puedes demostrar la solución. Eso no es cinismo; es cómo evitas “tunear” hasta generar un nuevo incidente.
Qué significa realmente “disco lento” en producción
Las discusiones sobre rendimiento de almacenamiento se descarrilan porque las personas mezclan métricas. Un desarrollador dice “el disco está lento” y se refiere a que la petición API está en timeout. Un sysadmin oye “disco lento” y revisa MB/s. Alguien de base de datos piensa en latencia de fsync. Todos tienen razón y están equivocados hasta que se anclan en la misma medición.
Cuatro métricas que importan (y una que engaña)
- IOPS: operaciones por segundo. Excelente para IO aleatorio y bloques pequeños. Inútil si ignoras la distribución de latencias.
- Throughput (MB/s): bueno para lecturas/escrituras en streaming y bloques grandes. Engañoso para bases de datos y cargas con mucho metadata.
- Latencia (media y percentiles): la única métrica que sienten tus usuarios. p99 importa más que “media”.
- Encolamiento: cuánto trabajo está esperando. El encolamiento suele ser donde nace lo “lento”.
- CPU iowait: el embustero. iowait puede estar bajo mientras la latencia es terrible (IO asíncrono), y alto mientras todo está bien (CPU idle, IO ocupado).
En Ubuntu 24.04 normalmente estás sobre una pila de bloque multi-cola (blk-mq). Eso significa múltiples colas hardware, múltiples caminos de envío software y comportamiento de planificador distinto al de los días de cola única. Es más rápido y más paralelo. También es más fácil crear un desastre si tu profundidad de cola no coincide con el dispositivo o el backend.
Una cita que debería estar en cada runbook de on-call:
“La esperanza no es una estrategia.” — Gene Kranz
Tunear almacenamiento sin medir es esperanza con bata de laboratorio.
Datos e historia interesantes (corto, concreto, útil)
- Linux solía elegir entre CFQ, deadline y noop; con blk-mq, muchos dispositivos ahora usan por defecto
none(omitir planificador) omq-deadline. - NVMe fue diseñado para colas profundas (muchos comandos en vuelo) porque el flash y PCIe prosperan con el paralelismo.
- Los HDD odian el IO aleatorio no porque sean “lentos”, sino porque son mecánicos: cada seek aleatorio es un pequeño desplazamiento físico.
- Deadline scheduling se popularizó para bases de datos porque evita el hambre y mantiene la latencia de lecturas contenida bajo carga de escrituras.
- El comportamiento de caché de escritura y flush cambió las reglas: los SSD modernos pueden reconocer escrituras rápido, pero un flush forzado (fsync/fua) aún puede costar tiempo real.
- El ajuste de la profundidad de cola se hizo mainstream con los SAN: demasiado poco desperdicia el array, demasiado lo derrite con cola y empeora la latencia de cola.
- Los volúmenes de bloque en la nube suelen imponer límites de rendimiento (IOPS/MB/s) independiente de lo que pueda hacer tu instancia, llevando a “disco lento” que en realidad es “cartera lenta”.
- Multi-queue se adoptó para escalar con núcleos CPU; una sola cerroja global de cola era un cuello de botella en SSDs rápidos.
- “noop” no era “sin scheduling” tanto como “fusión mínima”; tenía sentido para hardware que ya reordena eficientemente.
Planificador IO en Ubuntu 24.04: qué hace y cuándo importa
El planificador IO es el agente de tráfico entre tu sistema de archivos y tu dispositivo de bloque. Su trabajo es decidir qué se emite a continuación y en qué orden, y cuán agresivamente fusionar y despachar solicitudes.
En Linux moderno, especialmente con NVMe, el dispositivo y el firmware ya hacen mucho reordenamiento. Por eso “sin planificador” (none) puede ser la mejor opción: el kernel se hace a un lado. Pero “puede ser” no es “siempre es”.
Planificadores que verás comúnmente
- none: efectivamente omite el scheduling para dispositivos blk-mq; se apoya en el hardware para gestionar el orden. A menudo lo mejor para NVMe y arrays de almacenamiento de alto rendimiento.
- mq-deadline: versión multi-cola de deadline; procura acotar la latencia y evitar el hambre. Suele ser un buen predeterminado para SSDs SATA y cargas mixtas.
- kyber: orientado a baja latencia controlando el despacho según latencias objetivo. Puede ayudar en ciertos dispositivos; también puede confundir si no mides correctamente.
- bfq: orientado a equidad, a menudo para escritorios; puede ser útil para latencia interactiva en medios rotacionales, pero no es mi primera opción en servidores salvo que haya motivo.
Cuándo importa el planificador
Si tu carga es mayormente IO secuencial (grandes lecturas/escrituras en streaming), la elección del planificador rara vez cambia mucho. Si tu carga es mixta con aleatorio bajo contención (bases de datos, hosts VM, Ceph OSDs), el comportamiento del planificador y las colas puede dominar la latencia de cola.
Si estás sobre un controlador RAID hardware o un SAN que ya realiza scheduling sofisticado, añadir una capa pesada de planificador puede ser como pedir a dos project managers que “coordinen” el mismo sprint. Tendrás más reuniones, no más funcionalidades.
Profundidad de cola: la palanca oculta detrás del rendimiento y la latencia de cola
La profundidad de cola es cuántas solicitudes IO permites tener pendientes (en vuelo) a la vez. Más profundidad aumenta el paralelismo y puede mejorar throughput e IOPS. Demasiada profundidad aumenta el retardo por encolamiento y empeora la latencia, especialmente en la cola.
Tres colas que debes dejar de confundir
- Concurrencia de la aplicación: cuántos hilos, tareas asíncronas o procesos están emitiendo IO.
- Profundidad de cola del bloque del kernel: la capacidad de la capa de bloque para contener y despachar solicitudes (piensa en
nr_requestsy límites por dispositivo). - Profundidad de cola del dispositivo o backend: tamaño de cola NVMe, profundidad de HBA, límites LUN SAN, límites de volúmenes en la nube.
La profundidad “correcta” depende del workload y del dispositivo. Las bases de datos frecuentemente priorizan latencia acotada sobre throughput pico. Los trabajos de backup buscan throughput y toleran latencia. Los hosts VM quieren ambos, por eso generan discusiones.
Chiste #1: Ajustar la profundidad de cola es como el espresso—muy poco y no pasa nada, demasiado y nadie duerme, incluyendo tu array de almacenamiento.
Qué ocurre cuando la profundidad de cola es incorrecta
- Demasiado poca: verás baja utilización y throughput mediocre; el dispositivo podría hacer más pero no recibe suficiente trabajo paralelo.
- Demasiado alta: verás alta utilización, alto
aqu-sz, incremento deawaity p99 feo. No estás “ocupado”; estás congestionado.
Volúmenes en la nube y SANs: la profundidad de cola es política, no física
Con volúmenes tipo EBS, puedes tener un NVMe local rápido y aun así estar limitado por una política de dispositivo de bloque en red. Por eso cambiar el planificador en el invitado a veces ayuda menos que cambiar la clase de volumen, IOPS provisionados o el tipo de instancia.
Verificación: cómo hacer benchmarks sin engañarte
Si quieres verificar mejoras, necesitas una prueba que coincida con tu patrón IO de producción y un método que controle caché, calentamiento y concurrencia.
Reglas de benchmarking de almacenamiento que te mantienen empleado
- Elige un patrón IO: aleatorio vs secuencial, lectura vs escritura, tamaño de bloque, sync vs async, frecuencia de fsync.
- Usa percentiles: la latencia media es un cuento para dormir. p95/p99 es tu riesgo de outage.
- Controla la caché: la page cache puede hacer que las lecturas parezcan mágicas. Direct IO puede hacer que los sistemas de archivos parezcan peores que la realidad. Elige deliberadamente.
- Separa pruebas de dispositivo y de sistema de archivos: prueba el dispositivo crudo cuando sospechas del planificador/profundidad de cola; prueba el sistema de archivos cuando sospechas de journaling o opciones de montaje.
- Ejecuta lo suficiente: GC del SSD y throttling térmico pueden aparecer después de minutos, no segundos.
- Un cambio a la vez: esto no es un show de cocina.
Tu mejor amigo aquí es fio. No porque sea sofisticado, sino porque es explícito: puedes describir la carga y obtener estadísticas de latencia detalladas. Úsalo con iostat y los logs del kernel, y podrás decir si tu “mejora” es real.
Tareas prácticas: comandos, significado de la salida y decisiones
Estas son tareas reales que puedes ejecutar en Ubuntu 24.04. Cada una incluye qué observar y qué decisión tomar. Ejecútalas como root cuando sea necesario. Si estás en producción, haz primero las comprobaciones de bajo impacto.
Tarea 1: identifica los dispositivos de bloque reales y la topología
cr0x@server:~$ lsblk -e7 -o NAME,TYPE,SIZE,ROTA,TRAN,MODEL,SERIAL,MOUNTPOINTS
NAME TYPE SIZE ROTA TRAN MODEL SERIAL MOUNTPOINTS
nvme0n1 disk 1.8T 0 nvme Samsung SSD 980PRO S64DNE0R123456A
├─nvme0n1p1 part 512M 0 nvme /boot/efi
└─nvme0n1p2 part 1.8T 0 nvme /
Qué significa: ROTA=0 sugiere SSD/NVMe; TRAN te indica si es nvme, sata, sas, etc. Si estás en multipath o MD RAID, verás capas diferentes.
Decisión: Elige el dispositivo correcto para inspeccionar (nvme0n1 aquí). No ajustes la partición y olvides el disco subyacente.
Tarea 2: revisa logs del kernel por errores, resets, timeouts
cr0x@server:~$ sudo dmesg -T | egrep -i "nvme|scsi|blk_update_request|reset|timeout|I/O error" | tail -n 30
[Mon Dec 29 10:11:14 2025] nvme nvme0: I/O 123 QID 4 timeout, aborting
[Mon Dec 29 10:11:14 2025] nvme nvme0: Abort status: 0x371
[Mon Dec 29 10:11:15 2025] nvme nvme0: controller reset scheduled
Qué significa: Timeouts y resets no son “oportunidades de ajuste”. Son incidentes de fiabilidad. El rendimiento se vuelve caos cuando el dispositivo es inestable.
Decisión: Si ves esto, pausa los ajustes. Revisa firmware, cableado/backplane, errores PCIe AER, salud del controlador y avisos del proveedor. Si estás en la nube, abre un caso de soporte y considera mover el volumen.
Tarea 3: verifica el planificador actual por dispositivo
cr0x@server:~$ cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline kyber bfq
Qué significa: El planificador entre corchetes está activo (none aquí). Las opciones disponibles siguen.
Decisión: Para NVMe, none suele ser correcto. Si ves latencia de cola bajo carga mixta, prueba mq-deadline. No asumas nada.
Tarea 4: comprueba la profundidad básica de cola y límites de solicitudes
cr0x@server:~$ for f in nr_requests read_ahead_kb rotational rq_affinity nomerges; do echo -n "$f="; cat /sys/block/nvme0n1/queue/$f; done
nr_requests=256
read_ahead_kb=128
rotational=0
rq_affinity=1
nomerges=0
Qué significa: nr_requests limita las solicitudes encoladas en la capa de bloque. No es la única cola, pero importa para la congestión. read_ahead_kb influye en el comportamiento de lectura secuencial.
Decisión: Si tu throughput es bajo y la utilización del dispositivo no está al máximo, colas superficiales pueden ser sospechosas. Si tu latencia es mala con alta concurrencia, colas demasiado profundas pueden empeorar el encolamiento. Cambia con cautela y mide.
Tarea 5: verifica conteos y tamaños de colas hardware (NVMe)
cr0x@server:~$ sudo nvme id-ctrl /dev/nvme0 | egrep -i "mdts|sqes|cqes|oacs|oncs|nn|cntlid"
nn : 0x1
mdts : 0x9
cntlid: 0x0001
oacs : 0x0017
oncs : 0x001f
sqes : 0x66
cqes : 0x44
Qué significa: Las capacidades NVMe modelan cuán grandes pueden ser los IOs y qué características están soportadas. No es un número directo de “profundidad de cola”, pero te dice si el dispositivo se comporta como un NVMe moderno.
Decisión: Si las herramientas NVMe reportan rarezas, confirma que no estás detrás de un controlador que presenta NVMe en un modo extraño. Para la nube, confirma que realmente estás en NVMe y no en virtio-blk con comportamiento distinto.
Tarea 6: observa latencia y encolamiento en vivo con iostat
cr0x@server:~$ iostat -x -d 1 10 nvme0n1
Linux 6.8.0-xx-generic (server) 12/29/2025 _x86_64_ (32 CPU)
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
nvme0n1 120.0 3840.0 0.0 0.0 1.20 32.0 95.0 3040.0 0.0 0.0 8.50 32.0 1.05 32.0
Qué significa: r_await/w_await son latencias medias de lectura/escritura (ms). aqu-sz es el tamaño medio de la cola. %util es tiempo ocupado (no siempre fiable en dispositivos muy rápidos, pero aún una pista).
Decisión: Alto await con alto aqu-sz sugiere encolamiento: estás acumulando solicitudes. Alto await con baja encolación sugiere que el dispositivo/backend simplemente es lento por IO (o está ejecutando flushes).
Tarea 7: mapea “quién está haciendo IO” a una lista de procesos
cr0x@server:~$ sudo pidstat -d 1 5
Linux 6.8.0-xx-generic (server) 12/29/2025 _x86_64_ (32 CPU)
10:21:01 AM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
10:21:02 AM 1001 21455 0.00 51200.00 0.00 8 postgres
10:21:02 AM 0 1892 0.00 2048.00 0.00 1 systemd-journald
Qué significa: Esta es la forma más rápida de detectar una compactación desbocada, un trabajo de backup o una tormenta de logs.
Decisión: Si el “disco lento” coincide con un proceso dominando las escrituras, arregla la carga primero (limitar tasa, programar, mover) antes de tocar el planificador.
Tarea 8: confirma sistema de archivos y opciones de montaje
cr0x@server:~$ findmnt -no SOURCE,FSTYPE,OPTIONS /
/dev/nvme0n1p2 ext4 rw,relatime,errors=remount-ro
Qué significa: La elección del sistema de archivos y las opciones afectan IO de metadata, comportamiento de writeback y barriers.
Decisión: No copies opciones de montaje a ciegas. Si alguien deshabilitó barriers o forzó data=writeback “por rendimiento”, trátalo como un incidente de integridad de datos esperando un corte de energía.
Tarea 9: inspecciona reglas udev y perfiles tuned que puedan estar revirtiendo ajustes
cr0x@server:~$ systemctl is-enabled tuned 2>/dev/null || true
disabled
cr0x@server:~$ grep -R "queue/scheduler" -n /etc/udev/rules.d /lib/udev/rules.d 2>/dev/null | head
/lib/udev/rules.d/60-persistent-storage.rules:...
Qué significa: Podrías “establecer el scheduler” manualmente y tener udev o un perfil que lo revierta al reiniciar.
Decisión: Si los ajustes se siguen cambiando, deja de perseguir fantasmas. Haz la configuración persistente mediante reglas udev o cmdline del kernel donde corresponda, y documenta.
Tarea 10: cambia el planificador temporalmente (para una prueba controlada)
cr0x@server:~$ echo mq-deadline | sudo tee /sys/block/nvme0n1/queue/scheduler
mq-deadline
cr0x@server:~$ cat /sys/block/nvme0n1/queue/scheduler
none [mq-deadline] kyber bfq
Qué significa: El planificador cambió en vivo. Esto es reversible y debe probarse bajo la misma carga.
Decisión: Ejecuta tu benchmark y compara latencia p99 y throughput. Si ayuda, hazlo persistente. Si empeora, revierte y sigue buscando.
Tarea 11: prueba latencia y throughput del dispositivo crudo con fio (direct IO)
cr0x@server:~$ sudo fio --name=randread --filename=/dev/nvme0n1 --direct=1 --ioengine=io_uring --iodepth=32 --rw=randread --bs=4k --numjobs=1 --time_based=1 --runtime=60 --group_reporting
randread: (g=0): rw=randread, bs=(R) 4096B-4096B, (W) 4096B-4096B, ioengine=io_uring, iodepth=32
fio-3.36
Starting 1 process
randread: IOPS=210k, BW=820MiB/s (860MB/s)(49.1GiB/60s)
lat (usec): min=42, max=8500, avg=145.2, stdev=60.1
clat percentiles (usec):
| 1.00th=[ 70], 5.00th=[ 85], 10.00th=[ 95], 50.00th=[ 140],
| 90.00th=[ 210], 95.00th=[ 260], 99.00th=[ 420], 99.90th=[ 1100]
Qué significa: Esto evita la page cache y golpea el dispositivo. Obtienes percentiles que realmente reflejan la latencia de cola.
Decisión: Usa esto como tu baseline para “comportamiento del dispositivo”. Si el dispositivo es rápido aquí pero tu app es lenta, el cuello de botella está por encima de la capa de bloque (sistema de archivos, patrón de fsync, fragmentación, concurrencia de la app, almacenamiento en red).
Tarea 12: prueba rendimiento del sistema de archivos (incluye metadata y journaling)
cr0x@server:~$ mkdir -p /mnt/fio-test
cr0x@server:~$ sudo fio --name=fsyncwrite --directory=/mnt/fio-test --ioengine=io_uring --direct=0 --rw=write --bs=16k --numjobs=4 --iodepth=8 --fsync=1 --size=2G --group_reporting
fsyncwrite: (g=0): rw=write, bs=(R) 16384B-16384B, (W) 16384B-16384B, ioengine=io_uring, iodepth=8
fio-3.36
fsyncwrite: IOPS=9800, BW=153MiB/s (160MB/s)(8192MiB/53s)
clat percentiles (msec):
| 1.00th=[ 0.6], 5.00th=[ 0.9], 10.00th=[ 1.0], 50.00th=[ 2.1],
| 90.00th=[ 6.2], 95.00th=[ 9.5], 99.00th=[ 21.0], 99.90th=[ 45.0]
Qué significa: Esto se acerca más a un comportamiento tipo base de datos: muchos puntos de sync. La latencia de cola aquí es lo que mata transacciones.
Decisión: Si p99/p99.9 es alto aquí, el planificador y la profundidad de cola pueden ayudar, pero también revisa opciones del sistema de archivos, configuración del journal y escritores competidores.
Tarea 13: inspecciona dispositivos multipath/DM (si aplica)
cr0x@server:~$ lsblk -o NAME,TYPE,SIZE,PKNAME,MOUNTPOINTS | head
NAME TYPE SIZE PKNAME MOUNTPOINTS
dm-0 lvm 900G /
└─mpatha mpath 900G sdb
├─sdb disk 900G
└─sdc disk 900G
Qué significa: Las capas device-mapper pueden ocultar restricciones de profundidad de cola. Ajustar la capa equivocada no hace nada.
Decisión: Identifica los dispositivos hoja y confirma sus límites de cola y planificador allí. Para SANs, verifica la profundidad de HBA y ajustes de multipath. Si puedes notar un failover de ruta en gráficas de latencia, tienes trabajo por hacer.
Tarea 14: observa tiempo IO por dispositivo y merges con sar
cr0x@server:~$ sar -d 1 5
Linux 6.8.0-xx-generic (server) 12/29/2025 _x86_64_ (32 CPU)
10:24:01 AM DEV tps rkB/s wkB/s areq-sz aqu-sz await %util
10:24:02 AM nvme0n1 220.00 4096.00 6144.00 46.55 2.10 9.55 40.00
Qué significa: Otra vista del encolamiento y la utilización, útil para captura histórica.
Decisión: Si necesitas convencer a alguien, recoge sar durante la ventana del incidente. Los humanos creen en gráficas más que en argumentos.
Tarea 15: comprueba comportamiento de discard/TRIM (SSD) y evita mitos de “siempre activo”
cr0x@server:~$ lsblk -D -o NAME,DISC-GRAN,DISC-MAX,DISC-ZERO
NAME DISC-GRAN DISC-MAX DISC-ZERO
nvme0n1 512B 2G 0
Qué significa: El dispositivo soporta discard. Si montas con discard o ejecutas periódicamente fstrim importa.
Decisión: Prefiere fstrim periódico (vía systemd timer) sobre discard continuo para la mayoría de cargas de servidor. El discard continuo puede añadir overhead e impredecibilidad.
Tarea 16: verifica que tu “mejora” sea estable (repetibilidad)
cr0x@server:~$ sudo fio --name=randrw --filename=/dev/nvme0n1 --direct=1 --ioengine=io_uring --iodepth=16 --rw=randrw --rwmixread=70 --bs=4k --numjobs=2 --time_based=1 --runtime=180 --group_reporting
randrw: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, ioengine=io_uring, iodepth=16
fio-3.36
randrw: IOPS=120k, BW=469MiB/s (492MB/s)(84.1GiB/180s)
clat percentiles (usec):
| 95.00th=[ 600], 99.00th=[ 1200], 99.90th=[ 4200]
Qué significa: Una corrida mixta más larga captura GC, throttling y efectos de encolamiento.
Decisión: Si los resultados varían salvajemente entre ejecuciones, no tienes un problema de ajuste; tienes un problema de entorno (contención, throttling, variabilidad del backend o límites térmicos).
Tres microhistorias del mundo corporativo (anonimizadas, plausibles, técnicamente precisas)
1) Incidente causado por una suposición errónea: “NVMe siempre es rápido”
Un equipo migró un servicio sensible a latencia a servidores nuevos con unidades NVMe. El ticket decía “actualizamos el almacenamiento, el disco no puede ser el problema”. También habían hecho un nuevo deploy, así que la narrativa ya estaba escrita: debe ser el código de la aplicación.
La primera pista fue que la latencia media se veía bien, pero el servicio sufrió timeouts periódicos en peticiones. En el host, iostat mostró ráfagas donde aqu-sz se disparaba y w_await subía a dos dígitos. No era constante; venía en oleadas. El dispositivo era “rápido” hasta que no lo era.
La suposición de que “NVMe = baja latencia” escondió la historia real: la carga era intensiva en escrituras con fsyncs frecuentes, y el modelo de drive tenía una pequeña caché SLC. Bajo escrituras sostenidas, el drive transitaba a un estado sostenido más lento y entraba GC. Los logs del kernel estaban limpios. Sin errores. Solo un drive haciendo lo que los modelos tipo consumidor hacen cuando los usas como journal de base de datos.
Probaron otro planificador (mq-deadline) y redujeron ligeramente la concurrencia. La latencia de cola mejoró, pero la solución real fue aburrida: cambiar a una clase de SSD orientada a endurance y separar el WAL/journal a un dispositivo con comportamiento de escritura sostenida predecible.
La lección no fue “compra discos caros”. Fue “deja de tratar nombres de producto como garantías de rendimiento.” NVMe es un protocolo. Tu carga aún debe encajar con la física y el firmware.
2) Optimización que salió mal: “Aumentemos la profundidad de cola al máximo”
Un clúster de virtualización sufría bajo throughput en un LUN SAN compartido. Alguien leyó que “colas profundas aumentan IOPS”, lo cual es cierto del mismo modo que “más coches aumentan el flujo de tráfico” es cierto: a veces, hasta cierto punto.
Aumentaron ajustes relacionados con cola en múltiples capas: profundidad HBA, ajustes multipath y nr_requests en la capa de bloque. Las gráficas del clúster se vieron increíbles por un día. Luego el helpdesk empezó a recibir quejas de “la VM está congelada”. No lenta. Congelada.
La investigación mostró que la latencia media seguía aceptable, pero la p99 se fue por un precipicio bajo carga. El SAN estaba absorbiendo el paralelismo aumentado haciendo cola internamente. Cuando llegaba una ráfaga (backup más patching más alguien haciendo un informe), las colas internas crecían. Ese retardo por cola se traducía en esperas de IO de varios segundos para VMs desafortunadas. Las colas profundas hicieron que el SAN pareciera ocupado, no responsivo.
Retrocedieron. Reducir la profundidad de cola disminuyó el throughput pico pero mejoró la latencia de cola y eliminó la experiencia de “VM congelada”. El enfoque correcto fue fijar la profundidad de cola en lo que el array podía manejar con latencia acotada, y hacer aislamiento de carga (LUNs separados / QoS / programar backups).
Chiste #2: La “optimización” de la profundidad de cola es el único ajuste de rendimiento que también puede funcionar como generador improvisado de outages.
3) Práctica aburrida pero correcta que salvó el día: benchmarks base y disciplina de cambios
Un servicio con alta dependencia de almacenamiento tenía un ritual: en cada ciclo de actualización del kernel ejecutaban la misma pequeña suite de fio en un host de staging. Era aburrido. Nadie fue promovido por ello. Pero construyó una baseline con el tiempo: IOPS típicos, latencia p99 esperada y cómo variaba con elecciones de planificador.
Una semana, una actualización rutinaria de Ubuntu llegó. El equipo de app reportó “disco lento”, pero las gráficas eran ambiguas: CPU no estaba alta, la red no saturada y el dashboard de almacenamiento parecía normal. Normalmente aquí la gente discute y el incidente se alarga.
Como tenían baselines, inmediatamente rerunearon el mismo perfil de fio y vieron la latencia p99 aproximadamente duplicada para escrituras random 4k con la misma concurrencia. Eso acotó la búsqueda. Encontraron que la actualización había cambiado una regla udev aplicada a ciertas clases de dispositivo, cambiando el planificador de su ajuste elegido al predeterminado.
Lo corrigieron con una regla udev persistente y verificaron la corrección con el mismo benchmark. Sin heroísmos, sin conjeturas, sin “se siente más rápido ahora”. El postmortem fue corto y aburrido—exactamente lo que quieres.
La práctica no era ingeniosa. Era medición repetible y alcance mínimo de cambio. El tipo de aburrido que mantiene los pagers tranquilos.
Errores comunes: síntoma → causa raíz → solución
1) Síntoma: alto iowait, pero el disco parece inactivo
Causa raíz: El IO no está yendo al disco local que estás observando (sistema de archivos de red, LUN distinto, capa device-mapper), o el IO está bloqueado en flushes mientras el dispositivo reporta baja utilización.
Solución: Usa pidstat -d para encontrar el proceso culpable y findmnt para identificar el dispositivo subyacente. Si es NFS/Ceph/RBD, los cambios locales del planificador no ayudarán; céntrate en la latencia de la red/backend de almacenamiento.
2) Síntoma: gran throughput, pero latencia p99 terrible
Causa raíz: Profundidad de cola demasiado alta, o concurrencia de workload demasiado alta, causando retardo por encolamiento. A menudo visto en arrays compartidos y volúmenes en la nube.
Solución: Reduce la concurrencia (hilos de la app, iodepth, número de jobs) y/o elige un planificador que acote latencia (a menudo mq-deadline). Mide percentiles antes/después.
3) Síntoma: IOPS de lectura aleatoria muy por debajo de lo esperado en SSD/NVMe
Causa raíz: Profundidad de cola superficial, IO engine equivocado, o una prueba que accidentalmente usa IO con buffer y está dominada por page cache.
Solución: Usa fio con --direct=1, fija un iodepth razonable (p. ej., 16–64) y confirma el planificador. También verifica enlace PCIe y localidad NUMA si los resultados son extrañamente bajos.
4) Síntoma: las escrituras se vuelven lentas después de unos minutos de prueba
Causa raíz: Agotamiento de caché SLC del SSD, garbage collection, throttling térmico o presión del journal del sistema de archivos.
Solución: Extiende los benchmarks a 3–10 minutos y compara comportamiento temprano vs tardío. Revisa SMART/estado NVMe y temperatura. Si importa el rendimiento sostenido de escritura, usa SSDs empresariales y evita drives de consumidor para workloads con mucho journal.
5) Síntoma: cambiar el planificador parece no hacer nada
Causa raíz: Estás cambiando el planificador en la capa equivocada (partición vs disco, dm-crypt vs disco subyacente), o el dispositivo está controlado por RAID hardware/SAN que domina el orden.
Solución: Usa lsblk para encontrar el dispositivo hoja y verifica el planificador allí. En casos de RAID/SAN, céntrate en políticas del array, profundidad de HBA y aislamiento de carga.
6) Síntoma: stalls ocasionales de varios segundos con comportamiento normal el resto del tiempo
Causa raíz: Resets/timeouts de dispositivo, flaps de rutas multipath o problemas de firmware. A veces también commits del journal ext4 bajo contención patológica.
Solución: Revisa dmesg por mensajes de reset/timeout. Estabiliza el hardware/backend primero. Luego afina.
7) Síntoma: lecturas secuenciales lentas en arrays HDD
Causa raíz: Read-ahead demasiado pequeño, fragmentación o IO aleatorio competitivo. También posible: política de caché del controlador o RAID en reconstrucción/degradado.
Solución: Confirma salud del array, comprueba read-ahead y aisla cargas secuenciales de escritores aleatorios. No “arregles” esto profundizando colas a ciegas.
Listas de verificación / plan paso a paso
Checklist A: respuesta a incidente “Disco lento” (15–30 minutos)
- Confirma la ruta del dispositivo: usa
findmntylsblkpara asegurar que estás ajustando lo correcto. - Revisa errores: escanea
dmesgpor timeouts, resets o errores de IO. - Mide latencia/encolamiento en vivo:
iostat -x 1en el dispositivo relevante durante el problema. - Identifica procesos top en IO:
pidstat -d 1. Decide si el control de la carga es la solución real. - Establece si es problema de lectura o escritura: compara
r_awaitvsw_await, y considera cargas fsync-heavy. - Toma un benchmark baseline (si es seguro): una corrida corta de
fioque coincida con el patrón sospechado. - Sólo entonces prueba cambios de scheduler/cola: un parámetro a la vez, mide y anota resultados.
Checklist B: plan de ajuste controlado (amigable para change management)
- Elige una carga representativa: define tamaño de bloque, mezcla read/write, concurrencia y duración.
- Captura baseline: guarda salida de
fio, snapshots deiostaty versión del kernel. - Define criterios de éxito: p. ej., latencia p99 de escritura por debajo de X ms a Y IOPS; no sólo “se siente más rápido”.
- Prueba candidatos de planificador:
nonevsmq-deadline(y kyber si sabes por qué). - Prueba rango de profundidad de cola: varía
fio --iodepthy contador de jobs; no vayas directo a extremos. - Valida bajo contención: ejecuta una segunda carga concurrente si en producción normalmente hay IO mixto.
- Haz persistentes los cambios: una vez probado, implementa vía regla udev o configuración apropiada y documenta.
- Monitorea después del despliegue: observa p95/p99, no sólo promedios, por al menos un ciclo de negocio.
Hacer persistentes cambios de scheduler (sin sorpresas)
Cambios temporales con echo desaparecen al reiniciar. Para persistencia, las reglas udev son comunes. El criterio exacto de match depende del nombre del hardware. Prueba en staging primero.
cr0x@server:~$ sudo tee /etc/udev/rules.d/60-ioscheduler.rules >/dev/null <<'EOF'
ACTION=="add|change", KERNEL=="nvme0n1", ATTR{queue/scheduler}="mq-deadline"
EOF
cr0x@server:~$ sudo udevadm control --reload-rules
cr0x@server:~$ sudo udevadm trigger --name-match=nvme0n1
Decisión: Si no puedes hacer match confiable de dispositivos (porque los nombres cambian), hazlo por atributos como ID_MODEL o WWN. El objetivo es configuración estable, no una ruleta en el arranque.
FAQ
1) ¿Debo usar none o mq-deadline en NVMe?
Empieza con none para discos NVMe locales puros, luego prueba mq-deadline si te importa la latencia de cola bajo carga mixta. Elige el que mejore p99 para tu carga real.
2) ¿Qué profundidad de cola debo establecer?
No hay un único número. Para NVMe, colas más profundas pueden mejorar throughput. Para arrays compartidos/volúmenes en la nube, demasiada profundidad suele aumentar la p99. Ajusta midiendo: ejecuta fio con iodepth 1, 4, 16, 32, 64 y grafica percentiles de latencia.
3) ¿Por qué %util muestra 100% pero el throughput es bajo?
En dispositivos rápidos o cuando hay mucho encolamiento, %util puede reflejar “espera ocupada” y retraso por cola en lugar de throughput productivo. Mira await y aqu-sz para interpretarlo.
4) ¿Cambiar el planificador ayuda en RAID hardware?
A veces un poco, a menudo no mucho. Los controladores RAID hardware y los SANs hacen su propio scheduling y caching. En esos casos, céntrate en política de caché del controlador, alineación de stripe, salud del array y profundidad de HBA.
5) Mi app está lenta, pero fio crudo es rápido. ¿Ahora qué?
El cuello de botella está por encima del dispositivo: journaling del sistema de archivos, comportamiento de fsync, escrituras pequeñas sincronizadas, contención de metadata o el patrón IO de la aplicación. Ejecuta pruebas fio a nivel de sistema de archivos e inspecciona opciones de montaje y writeback.
6) ¿Es bfq alguna vez apropiado en servidores?
Rara vez, pero no nunca. Si necesitas equidad entre fuentes IO competidoras (sistemas multi-tenant) y puedes permitir el overhead, puede ayudar. Para la mayoría de cargas servidor, empieza con none o mq-deadline y solo difiere con evidencia.
7) ¿Debo montar ext4 con discard para rendimiento SSD?
Usualmente no. Prefiere trim periódico (fstrim.timer) para overhead predecible. El discard continuo puede añadir variación de latencia.
8) ¿Puedo “arreglar” disco lento aumentando read-ahead?
Solo si la carga es realmente lecturas secuenciales y el cuello de botella es thrash de readahead. Para cargas aleatorias (bases de datos), aumentar read-ahead suele desperdiciar caché y empeorar las cosas. Mide hit rate de page cache y patrón de carga antes de tocarlo.
9) ¿Por qué cambia el rendimiento después de reiniciar?
Perdiste ajustes no persistentes, cambió el nombre del dispositivo, o udev/tuned aplicaron políticas distintas. Haz los cambios persistentes y verifica después del reinicio con el mismo benchmark.
10) ¿io_uring siempre es el mejor engine para fio?
Es un buen predeterminado en kernels modernos, pero no universal. En algunos entornos o drivers, libaio es más comparable a apps legacy. Usa el engine que coincida con tu stack IO de producción.
Conclusión: próximos pasos que sobreviven al control de cambios
Ubuntu 24.04 no está saboteando tus discos en secreto. La mayoría de problemas de “disco lento” se reducen a tres cosas: mides la capa equivocada, estás encolando demasiado (o muy poco), o el dispositivo/backend está enfermo y ajustar es una distracción.
Haz esto a continuación, en orden:
- Captura evidencia: fragmentos de
iostat -x,pidstat -dydmesgdurante la ralentización. - Baseline con fio: una prueba raw-device y una prueba a nivel de sistema de archivos que se parezcan a producción.
- Prueba elecciones de planificador:
nonevsmq-deadlinees el punto pragmático de partida. Verifica con percentiles. - Ajusta concurrencia/profundidad de cola deliberadamente: ajusta primero la paralelización del workload; toca los knobs del kernel solo si puedes explicar por qué.
- Hazlo persistente y documentado: si la corrección no es reproducible tras reiniciar, no es una corrección; es una demo.
El objetivo no es ganar un benchmark. Es hacer que el sistema sea predecible en el peor día que definitivamente vas a tener.