From dae1f4bf0e3f0a4c1e1a01b423e7b0f5f442d17a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 13 Sep 2026 11:53:27 +0200 Subject: [PATCH 1/2] Use the kyber I/O scheduler on real disks The kernel leaves NVMe on none and everything else on mq-deadline. Neither bounds latency once the device queue fills, so a large build, copy, or package upgrade can make the desktop sluggish while reads wait behind a wall of writes. Kyber keeps separate read and sync-write queues and throttles the depth it submits to hit a 2ms read target, which keeps interactive reads flowing under heavy writes at negligible CPU cost. The trade is a small ceiling on peak throughput on very fast devices, which matters for a storage server chasing IOPS but not for a desktop. Ships as a package-owned udev rule in /etc so it applies at boot and on hot-plug. Existing installs pick it up at the next boot. zram is left alone since it has nothing to schedule. Co-Authored-By: Claude Fable 5.1 --- etc/udev/rules.d/60-omarchy-io-scheduler.rules | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 etc/udev/rules.d/60-omarchy-io-scheduler.rules diff --git a/etc/udev/rules.d/60-omarchy-io-scheduler.rules b/etc/udev/rules.d/60-omarchy-io-scheduler.rules new file mode 100644 index 00000000..7f83f6ce --- /dev/null +++ b/etc/udev/rules.d/60-omarchy-io-scheduler.rules @@ -0,0 +1,6 @@ +# Kyber targets read latency (2ms) and throttles writes to hold it, so the +# desktop stays responsive under a large build, copy, or package upgrade. +# The kernel default is none for NVMe and mq-deadline for everything else, +# neither of which bounds latency once the device queue fills. Applies to real +# disks only; zram is memory and has nothing to schedule. +ACTION=="add|change", KERNEL=="nvme[0-9]*n[0-9]*|sd[a-z]*|mmcblk[0-9]*|vd[a-z]*", ATTR{queue/scheduler}="kyber" From 795ac57a6c65ed1943c62739efb228bffce048bd Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 13 Sep 2026 12:09:50 +0200 Subject: [PATCH 2/2] Match only whole disks in the kyber udev rule udev patterns are shell globs, so nvme[0-9]*n[0-9]* also matched every partition. Partitions have no queue/scheduler, and udev logged a "Could not chase sysfs attribute" for each one at boot. Restrict the match to SUBSYSTEM block with DEVTYPE disk, which also keeps mmcblk boot areas and NVMe multipath nodes out. Reword the comment: kyber targets a read latency rather than bounding it, and the kernel's default choice depends on the device rather than being a fixed NVMe versus everything-else split. Co-Authored-By: Claude Fable 5.1 --- etc/udev/rules.d/60-omarchy-io-scheduler.rules | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/etc/udev/rules.d/60-omarchy-io-scheduler.rules b/etc/udev/rules.d/60-omarchy-io-scheduler.rules index 7f83f6ce..1f389f4b 100644 --- a/etc/udev/rules.d/60-omarchy-io-scheduler.rules +++ b/etc/udev/rules.d/60-omarchy-io-scheduler.rules @@ -1,6 +1,8 @@ -# Kyber targets read latency (2ms) and throttles writes to hold it, so the -# desktop stays responsive under a large build, copy, or package upgrade. -# The kernel default is none for NVMe and mq-deadline for everything else, -# neither of which bounds latency once the device queue fills. Applies to real -# disks only; zram is memory and has nothing to schedule. -ACTION=="add|change", KERNEL=="nvme[0-9]*n[0-9]*|sd[a-z]*|mmcblk[0-9]*|vd[a-z]*", ATTR{queue/scheduler}="kyber" +# Kyber keeps reads in their own queue and throttles the depth it submits to +# hold a 2ms read latency target, so interactive reads keep flowing while a +# large build, copy, or package upgrade floods the disk with writes. The +# kernel's own pick (none or mq-deadline, depending on the device) does not +# regulate latency once the queue fills. Whole disks only: partitions have no +# scheduler of their own, and zram is memory with nothing to schedule. Zoned +# btrfs disks are moved back to mq-deadline by 64-btrfs-zoned.rules. +ACTION=="add|change", SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", KERNEL=="nvme*|sd*|mmcblk*|vd*", ATTR{queue/scheduler}="kyber"