{ config, lib, pkgs, service_configs, ... }: let hddTuneIosched = pkgs.writeShellScript "hdd-tune-iosched" '' # Called by udev with the partition kernel name (e.g. sdb1). # Derives the parent disk and applies mq-deadline iosched params. parent=''${1%%[0-9]*} dev="/sys/block/$parent" [ -d "$dev/queue/iosched" ] || exit 0 echo 500 > "$dev/queue/iosched/read_expire" echo 15000 > "$dev/queue/iosched/write_expire" echo 128 > "$dev/queue/iosched/fifo_batch" echo 16 > "$dev/queue/iosched/writes_starved" echo 4096 > "$dev/queue/max_sectors_kb" 2>/dev/null || true ''; in { boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usb_storage" "usbhid" "sd_mod" ]; boot.initrd.kernelModules = [ "dm-snapshot" ]; boot.kernelModules = [ "kvm-amd" ]; boot.extraModulePackages = [ ]; swapDevices = [ ]; hardware.cpu.amd.updateMicrocode = true; hardware.enableRedistributableFirmware = true; # HDD I/O tuning for torrent seeding workload (high-concurrency random reads) # sharing the pool with latency-sensitive sequential reads (Jellyfin playback). # # mq-deadline sorts requests into elevator sweeps, reducing seek distance. # read_expire=500ms keeps reads bounded so a Jellyfin segment can't queue for # seconds behind a torrent burst; write_expire=15s lets the scheduler batch # writes for coalescence (torrent writes are async and tolerate delay). # The bulk of read coalescence already happens above the scheduler via ZFS # aggregation (zfs_vdev_aggregation_limit=4M, read_gap_limit=128K, # async_read_max=32), so the scheduler deadline only needs to be large enough # to keep the elevator sweep coherent -- 500ms is plenty on rotational disks. # fifo_batch=128 keeps sweeps long; writes_starved=16 heavily favors reads. # 4 MiB readahead matches libtorrent piece extent affinity for sequential prefetch. # # The NixOS ZFS module hardcodes a udev rule that forces scheduler="none" on all # ZFS member partitions' parent disks (on both add AND change events). We counter # it with lib.mkAfter so our rule appears after theirs in 99-local.rules — our # rule matches the same partition events and sets mq-deadline back, then a RUN # script applies the iosched params. Only targets rotational, non-removable disks # (i.e. HDDs, not SSDs or USB). services.udev.extraRules = lib.mkAfter '' ACTION=="add|change", KERNEL=="sd[a-z]*[0-9]*", ENV{ID_FS_TYPE}=="zfs_member", ATTR{../queue/rotational}=="1", ATTR{../removable}=="0", ATTR{../queue/scheduler}="mq-deadline", ATTR{../queue/read_ahead_kb}="4096", ATTR{../queue/nr_requests}="512", RUN+="${hddTuneIosched} %k" ''; }