Linux I/O Schedulers: mq-deadline, BFQ, Kyber, and none
Between your application writing a file and the bytes reaching the disk sits a queue, and a scheduler that decides what order that queue is drained in. On some hardware this matters enormously and on other hardware it should be switched off.
Seeing what you have
cat /sys/block/sda/queue/scheduler
# [mq-deadline] kyber bfq none
cat /sys/block/nvme0n1/queue/scheduler
# [none] mq-deadline kyber bfq
The bracketed entry is active. Also useful:
cat /sys/block/sda/queue/rotational
# 1 for spinning, 0 for flash
The four options
none does no reordering. Requests go to the device in the order they arrive. This is the sensible default for NVMe, which has deep hardware queues and does its own ordering far better than software can.
mq-deadline imposes deadlines so no request starves, with a modest amount of reordering and merging. A good general default for SATA SSDs and a reasonable one for spinning disks.
BFQ, Budget Fair Queueing, allocates I/O fairly between processes and prioritises interactive work. It is the most sophisticated and has the highest CPU cost. It is also the only common scheduler that properly honours ionice priorities.
Kyber targets low latency with a small implementation, aimed at fast devices where you still want some control.
The rule of thumb
Spinning disks: BFQ or mq-deadline. Seeks are expensive, so reordering to minimise head movement is a genuine and large win.
SATA SSDs: mq-deadline. No seek cost, and some merging still helps.
NVMe: none. The device has multiple deep hardware queues and reorders internally. A software scheduler in front mostly adds latency.
Desktop with heavy background I/O: BFQ, on any device type. If a backup or a large copy makes your machine unusable, BFQ is what fixes that, and it is why some desktop-focused distributions default to it.
Why ionice sometimes does nothing
This is the practical consequence people actually hit.
ionice -c 3 requests the idle class, and that only means something if the scheduler implements priority classes. none does no scheduling at all and mq-deadline largely ignores them. BFQ honours them.
So the same command that transforms a spinning disk does nothing on NVMe with none. It is not a broken command; it is a scheduler that was never asked to care.
If you want ionice to work, switch that device to BFQ.
Changing it
Immediately, until reboot:
echo bfq | sudo tee /sys/block/sda/queue/scheduler
Permanently, with a udev rule, which is the correct way because it applies per device automatically:
sudo tee /etc/udev/rules.d/60-scheduler.rules > /dev/null <<'RULES'
# Spinning disks get BFQ
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="bfq"
# SATA SSDs get mq-deadline
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="mq-deadline"
# NVMe gets none
ACTION=="add|change", KERNEL=="nvme[0-9]n[0-9]", ATTR{queue/scheduler}="none"
RULES
sudo udevadm control --reload-rules
sudo udevadm trigger
Matching on queue/rotational means the rule applies correctly to whatever is plugged in, without maintaining a list of device names.
A kernel parameter also works and applies globally, which is less useful on a machine with mixed storage:
elevator=bfq
Measuring rather than guessing
Scheduler advice is full of confident claims that were measured on different hardware with a different workload. Test yours:
# Throughput
fio --name=seqread --rw=read --bs=1M --size=2G --numjobs=1 --runtime=30 --time_based
# Latency under concurrency, which is where schedulers differ most
fio --name=randread --rw=randread --bs=4k --iodepth=32 --size=2G --runtime=30 --time_based
Our fio guide covers this properly. Run the same job under each scheduler and compare, rather than adopting a setting from a forum post about hardware you do not own.
Virtual machines
Inside a guest, the host is already scheduling. Doubling up adds latency for no benefit, so none inside guests and real scheduling on the host is the usual recommendation. Our KVM explainer covers the layers.
Frequently Asked Questions
What does an I/O scheduler do?
It decides the order in which queued requests are sent to a storage device, and whether to merge adjacent ones. On a spinning disk that reordering avoids expensive seeks. On fast flash there are no seeks, so the reordering can cost more than it saves.
Which scheduler should I use for an NVMe drive?
Usually none, which is the common default. NVMe devices have deep internal queues and handle ordering themselves, so adding a software scheduler mostly adds latency. Use BFQ instead only if desktop responsiveness under heavy I/O matters more than raw throughput.
Why does ionice have no effect on my system?
Because ionice priorities need a scheduler that implements them, and the none and mq-deadline schedulers largely do not. BFQ honours them properly. This is why the same ionice command transforms a spinning disk and does nothing on NVMe.
How do I change the scheduler permanently?
Write a udev rule that sets it per device class, since the sysfs setting is lost at reboot. Matching on the rotational attribute lets you apply one scheduler to spinning disks and another to flash automatically.
Is BFQ slower than mq-deadline?
For raw sequential throughput, often slightly. For interactive responsiveness while something else is hammering the disk, it is considerably better. Which one is faster depends entirely on what you are measuring.
Does the scheduler matter for virtual machines?
Inside a guest, usually not, because the host is doing the real scheduling and doubling up adds latency. Setting none inside guests and letting the host schedule is the common recommendation.