El programador de E/S (I/O scheduler) es la parte del kernel que decide en qué orden se envían al disco las peticiones de lectura y escritura. En discos mecánicos, reordenar y agrupar peticiones marca una gran diferencia; en NVMe y en discos virtuales, casi cualquier trabajo extra solo añade latencia. En este tutorial verás qué programadores ofrece Ubuntu 24.04, cambiarás el de un disco en caliente, medirás el efecto con fio y dejarás la elección fija con una regla de udev.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS o un servidor bare metal de CubePath.
- Un usuario no root con privilegios
sudo. - Unos 4 GB libres en el disco que vas a probar, para el archivo de pruebas de
fio.
Paso 1: Identificar los discos y el programador actual
Lista los discos físicos o virtuales del servidor. La columna ROTA vale 1 para discos mecánicos y 0 para SSD, NVMe y la mayoría de discos virtuales:
lsblk -d -o NAME,ROTA,SIZE,MODEL
NAME ROTA SIZE MODEL
sda 1 7.3T ST8000NM017B
nvme0n1 0 894.3G SAMSUNG MZ1L2960HCJR
En un VPS verás normalmente un disco vda (virtio-blk) o sda (virtio-scsi) con ROTA a 0 o 1 según lo que informe el hipervisor.
Consulta el programador de cada disco. El que está entre corchetes es el activo:
grep . /sys/block/*/queue/scheduler
/sys/block/nvme0n1/queue/scheduler:[none] mq-deadline
/sys/block/sda/queue/scheduler:[mq-deadline] none
Ubuntu 24.04 usa por defecto none en dispositivos con varias colas de hardware (NVMe, la mayoría de discos virtio) y mq-deadline en el resto. Solo aparecen los programadores cuyo módulo está cargado; bfq y kyber se cargan bajo demanda.
Paso 2: Conocer los programadores disponibles
Los programadores antiguos (noop, deadline, cfq) desaparecieron en el kernel 5.0 junto con la capa de bloques de una sola cola. Si una guía te pide escribir cfq o noop, no funcionará en Ubuntu 24.04. Los actuales son:
| Programador | Qué hace | Indicado para |
|---|---|---|
none | Envía las peticiones en orden de llegada, sin reordenar. Mínimo uso de CPU. | NVMe, SSD rápidos, discos virtuales y RAID por hardware con caché. |
mq-deadline | Agrupa peticiones y asigna a cada una un plazo, dando prioridad a las lecturas. | Discos mecánicos y SSD SATA con cargas de servidor. |
bfq | Reparte el ancho de banda de forma justa entre procesos y prioriza la interactividad. Consume más CPU por petición. | Discos lentos compartidos por muchos procesos, escritorios. |
kyber | Ajusta la profundidad de las colas para cumplir un objetivo de latencia de lectura y escritura. | Dispositivos rápidos donde importa la latencia de cola bajo carga mixta. |
Para cargar los módulos de bfq y kyber y que aparezcan en la lista:
sudo modprobe bfq
sudo modprobe kyber-iosched
cat /sys/block/sda/queue/scheduler
[mq-deadline] kyber bfq none
Notaen un VPS, el hipervisor ya planifica las E/S hacia los discos físicos. Añadir otro programador dentro de la máquina virtual rara vez ayuda, así que
nonesuele ser la mejor opción. Mídelo antes de cambiarlo.
Paso 3: Medir con fio
Cambiar de programador sin medir es adivinar. fio genera cargas de E/S reproducibles e informa de IOPS y percentiles de latencia:
sudo apt update
sudo apt install fio
Crea un archivo de trabajo en /etc/fio/ que simule una carga típica de base de datos: 70 % lecturas y 30 % escrituras aleatorias de 4 KB, saltándose la caché de página con direct=1 para medir el disco y no la RAM:
sudo mkdir -p /etc/fio
sudo nano /etc/fio/mixto.fio
[global]
ioengine=libaio
direct=1
bs=4k
size=4G
runtime=60
time_based=1
group_reporting=1
filename=/var/tmp/fio.test
[mixto]
rw=randrw
rwmixread=70
iodepth=32
numjobs=4
Cambia filename si el disco que quieres probar no contiene /var/tmp (por ejemplo, /srv/datos/fio.test para un disco montado en /srv/datos). Ejecuta la prueba con el programador actual:
sudo fio /etc/fio/mixto.fio
De toda la salida, fíjate en las líneas de IOPS y en los percentiles de latencia de lectura:
read: IOPS=11.8k, BW=46.2MiB/s (48.4MB/s)(2771MiB/60001msec)
write: IOPS=5071, BW=19.8MiB/s (20.8MB/s)(1189MiB/60001msec)
clat percentiles (usec):
| 1.00th=[ 202], 5.00th=[ 297], 10.00th=[ 375], 20.00th=[ 553],
| 50.00th=[ 1336], 90.00th=[ 4555], 95.00th=[ 6456],
| 99.00th=[12125], 99.50th=[15270], 99.90th=[24249]
Anota los IOPS y los percentiles 50 y 99. Estos valores son un ejemplo; los tuyos dependerán del disco.
Paso 4: Cambiar el programador en caliente y comparar
Escribe el nombre del programador en el archivo scheduler del disco. El cambio es inmediato, no requiere desmontar nada y se pierde al reiniciar:
echo bfq | sudo tee /sys/block/sda/queue/scheduler
cat /sys/block/sda/queue/scheduler
bfq
mq-deadline kyber [bfq] none
Repite la prueba del paso 3 con cada candidato (none, mq-deadline, bfq, kyber) y compara las cifras. Como orientación, en pruebas así suelen salir estos patrones:
- NVMe:
noneda los mejores IOPS.mq-deadlineykyberquedan cerca;bfqpierde claramente por el coste de CPU. - Disco mecánico:
mq-deadlineybfqsuperan anoneporque ordenan las peticiones y reducen los movimientos del cabezal.bfqmejora la latencia cuando varios procesos compiten por el disco. - Disco virtual: las diferencias suelen ser pequeñas y
nonegana o empata.
Si dos programadores dan cifras similares, quédate con el más simple.
Ajustar los parámetros de mq-deadline
Cada programador expone parámetros en /sys/block/<disco>/queue/iosched/. Para mq-deadline, los más útiles son read_expire y write_expire (plazo máximo en milisegundos antes de atender una lectura o escritura, 500 y 5000 por defecto) y writes_starved (cuántas tandas de lecturas se atienden antes de dar paso a las escrituras pendientes, 2 por defecto):
echo mq-deadline | sudo tee /sys/block/sda/queue/scheduler
grep . /sys/block/sda/queue/iosched/{read_expire,write_expire,writes_starved,fifo_batch}
/sys/block/sda/queue/iosched/read_expire:500
/sys/block/sda/queue/iosched/write_expire:5000
/sys/block/sda/queue/iosched/writes_starved:2
/sys/block/sda/queue/iosched/fifo_batch:16
Por ejemplo, para reducir la latencia de lectura en un disco mecánico con una base de datos, prueba a bajar read_expire:
echo 250 | sudo tee /sys/block/sda/queue/iosched/read_expire
Vuelve a medir con fio. Si el percentil 99 de lectura no mejora, restaura el valor por defecto.
Revisar la lectura anticipada
Independientemente del programador, read_ahead_kb controla cuántos datos lee el kernel por adelantado en accesos secuenciales. El valor por defecto es 128 KB:
cat /sys/block/sda/queue/read_ahead_kb
Subirlo (por ejemplo a 1024) ayuda en discos mecánicos que sirven archivos grandes de forma secuencial, como vídeo o copias de seguridad. En cargas aleatorias de base de datos no aporta nada o empeora, porque lee datos que nadie va a usar.
Paso 5: Hacer el cambio persistente con udev
El antiguo parámetro de arranque elevator= ya no tiene efecto en kernels con la capa de bloques multicola, así que la forma correcta de fijar un programador es una regla de udev. Crea el archivo:
sudo nano /etc/udev/rules.d/60-ioscheduler.rules
Este ejemplo asigna mq-deadline a los discos mecánicos y none a los NVMe y demás discos no rotacionales. Adáptalo al resultado de tus pruebas:
# Discos mecánicos
ACTION=="add|change", SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", KERNEL=="sd[a-z]*", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="mq-deadline"
# SSD SATA y discos virtuales no rotacionales
ACTION=="add|change", SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", KERNEL=="sd[a-z]*|vd[a-z]*", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="none"
# NVMe
ACTION=="add|change", SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", KERNEL=="nvme[0-9]*n[0-9]*", ATTR{queue/scheduler}="none"
La condición ENV{DEVTYPE}=="disk" excluye las particiones, que no tienen programador propio. Si elegiste bfq o kyber, añade también el módulo a /etc/modules-load.d/ para que esté cargado cuando se aplique la regla:
echo bfq | sudo tee /etc/modules-load.d/bfq.conf
Recarga las reglas y aplícalas a los discos existentes sin reiniciar:
sudo udevadm control --reload-rules
sudo udevadm trigger --subsystem-match=block --action=change
Comprueba el resultado:
grep . /sys/block/*/queue/scheduler
/sys/block/nvme0n1/queue/scheduler:[none] mq-deadline kyber bfq
/sys/block/sda/queue/scheduler:[mq-deadline] kyber bfq none
Para confirmar que la regla sobrevive a un reinicio, reinicia el servidor en una ventana de mantenimiento y vuelve a ejecutar el mismo comando.
Los ajustes de iosched/ como read_expire se pueden persistir de la misma forma, añadiendo por ejemplo ATTR{queue/iosched/read_expire}="250" al final de la regla del disco correspondiente, siempre después de la asignación del programador.
Paso 6: Vigilar el disco en producción
Las pruebas sintéticas no sustituyen a la carga real. iostat, del paquete sysstat, muestra la latencia media y la ocupación de cada disco:
sudo apt install sysstat
iostat -dx 5
La salida real tiene más columnas; estas son las relevantes:
Device r/s w/s rkB/s wkB/s r_await w_await aqu-sz %util
nvme0n1 812.4 305.2 3249.6 4880.0 0.18 0.05 0.17 14.20
sda 96.0 41.8 6144.0 2140.0 7.95 12.40 1.28 78.60
r_awaityw_await: tiempo medio en milisegundos de cada lectura y escritura, incluyendo el tiempo en cola. Es la cifra que el programador influye directamente.aqu-sz: número medio de peticiones en cola.%util: porcentaje de tiempo con alguna petición en curso. En discos mecánicos, valores cercanos al 100 % indican saturación; en NVMe no es fiable porque atiende muchas peticiones en paralelo.
Compara estas cifras antes y después de cambiar el programador, en horas de carga similares.
Solución de problemas
tee: /sys/block/sda/queue/scheduler: Invalid argument: el nombre no existe o su módulo no está cargado. Ejecuta sudo modprobe bfq o sudo modprobe kyber-iosched y vuelve a intentarlo. Recuerda que cfq, noop y deadline ya no existen.
La regla de udev no se aplica tras reiniciar: comprueba la sintaxis con udevadm test /sys/block/sda 2>&1 | grep -i scheduler y revisa que el nombre del disco coincide con el patrón KERNEL==. En discos virtio el nombre es vda, no sda.
Los resultados de fio varían mucho entre ejecuciones: aumenta runtime a 120 segundos y repite cada prueba tres veces. En un VPS, otras máquinas del mismo host también afectan a las cifras.
Conclusión
Has identificado el programador de E/S de cada disco, comparado los disponibles con una carga reproducible de fio y fijado la elección con una regla de udev, sin depender del obsoleto parámetro elevator=. En la mayoría de servidores modernos la respuesta será none para NVMe y discos virtuales y mq-deadline para discos mecánicos, pero ahora tienes datos para confirmarlo.
Como siguientes pasos, puedes borrar el archivo de pruebas con sudo rm /var/tmp/fio.test, ajustar las opciones de montaje del sistema de archivos (noatime) o revisar los parámetros vm.dirty_* del kernel para controlar cómo se vuelcan las escrituras al disco.
