...

I/O Scheduler Linux: Noop, mq-deadline & BFQ im Hosting erklärt

Der I/O Scheduler Linux entscheidet, wie das System Lese- und Schreibzugriffe auf SSD, NVMe und HDD sortiert, priorisiert und zum Gerät sendet. In diesem Leitfaden erkläre ich praxisnah, wann Noop, mq-deadline und BFQ im Hosting die beste Wahl sind – inklusive Tuning, Tests und klaren Handlungsschritten.

Zentrale Punkte

  • Noop: Minimaler Overhead auf SSD/NVMe und in VMs
  • mq-deadline: Ausgewogene Latenz und Durchsatz für Server
  • BFQ: Fairness und schnelle Reaktion bei Multi-User
  • blk-mq: Multi-Queue-Design für moderne Hardware
  • Tuning: Tests je Workload statt fester Regeln

Wie der I/O Scheduler im Linux-Hosting wirkt

Ein Linux-I/O-Scheduler ordnet I/O-Anfragen in Queues, führt Merging durch und entscheidet die Auslieferung zum Gerät, um Latenz zu senken und Durchsatz zu heben. Moderne Kernel nutzen blk-mq, also Multi-Queue, damit mehrere CPU-Kerne parallel I/O anstoßen können. Das passt zu NVMe-SSDs, die viele Queues und hohe Parallelität bieten und so Warteschlangen verkürzen. Im Hosting treffen oft breite Mischlasten aufeinander: Webserver liefern viele kleine Reads, Datenbanken erzeugen Sync-Writes, Backups erzeugen Streams. Der passende Scheduler reduziert Staus, hält Antwortzeiten stabil und schützt die Server-Erfahrung unter Last.

blk-mq in der Praxis: none vs. noop und Kernel-Defaults

Seit Kernel 5.x ist das Multi-Queue-Design der Standardpfad. Dabei ist none der „Noop“-Äquivalent für blk-mq, während noop historisch aus dem Single-Queue-Pfad stammt. Auf NVMe-Geräten ist meist nur none verfügbar; auf SATA/SAS sieht man häufig mq-deadline, optional bfq und je nach Distribution auch kyber. Die Defaults variieren: NVMe startet in aller Regel mit none, SCSI/SATA oft mit mq-deadline. Ich prüfe daher immer die verfügbaren Optionen via cat /sys/block/<dev>/queue/scheduler und entscheide pro Gerät. Wo nur none auswählbar ist, ist das gewollt – zusätzliche Sortierung bringt dort praktisch keinen Mehrwert.

Noop im Servereinsatz: Wann der Minimalismus gewinnt

Noop führt vor allem Merging benachbarter Blöcke durch, sortiert jedoch nicht, was den CPU-Overhead extrem gering hält. Auf SSDs und NVMe übernehmen Controller und Firmware die clevere Reihenfolge, sodass zusätzliche Sortierung im Kernel kaum Nutzen bringt. In VMs und Containern plane ich oft Noop ein, weil der Hypervisor ohnehin übergreifend plant. Auf Rotationsplatten verzichte ich auf Noop, da fehlende Sortierung dort Seek-Zeiten erhöht. Wer den Hardware-Kontext sicher abgrenzen will, schaut zuerst auf den Speicher-Typ – hier hilft ein Blick auf NVMe, SSD und HDD, bevor ich den Scheduler festlege.

mq-deadline: Fristen, Reihenfolgen und klare Prioritäten

mq-deadline gibt Lesezugriffen kurze Deadlines und lässt Schreibzugriffe etwas länger warten, um Antwortzeit spürbar zu sichern. Der Scheduler sortiert zudem nach Block-Adressen und senkt damit Suchzeiten, was vor allem HDDs und RAID-Verbünden hilft. In Web- und Datenbank-Hosts liefert mq-deadline eine gute Balance aus Latenz und Durchsatz. Ich setze ihn gern ein, wenn Workloads gemischt sind und sowohl Reads als auch Writes dauerhaft anstehen. Für das Feintuning prüfe ich Request-Tiefe, Writeback-Verhalten und Controller-Cache, damit die Deadline-Logik konsistent greift.

BFQ: Fairness und Reaktionsfreude für viele gleichzeitige Nutzer

BFQ verteilt die Bandbreite proportional und vergibt Budgets pro Prozess, was spürbar fair wirkt, wenn viele User parallel I/O erzeugen. Interaktive Tasks wie Admin-Shells, Editoren oder API-Calls bleiben flott, obwohl im Hintergrund Backups laufen. Auf HDDs erreicht BFQ oft hohe Effizienz, weil es sequentielle Phasen ausnutzt und kurze Idle-Fenster klug einsetzt. Auf sehr schnellen SSDs entsteht ein wenig Zusatzaufwand, den ich gegen die spürbare Reaktionsfreude abwäge. Wer Cgroups und ioprio nutzt, kann mit BFQ klare Zusicherungen herstellen und so Ärger durch laute Nachbarn vermeiden.

QoS im Alltag: ioprio, ionice und Cgroups v2 mit BFQ

Für saubere Priorisierung kombiniere ich BFQ mit Prozess- und Cgroup-Regeln. Auf Prozessebene setze ich mit ionice Klassen und Prioritäten: ionice -c1 (Realtime) für Latenz-kritische Reads, ionice -c2 -n7 (Best-Effort, niedrig) für Backups oder Index-Läufe, ionice -c3 (Idle) für alles, das nur in Leerlaufzeiten laufen soll. In Cgroups v2 nutze ich io.weight für relative Anteile (z. B. 100 vs. 1000) und io.max für harte Limits, etwa echo "259:0 rbps=50M wbps=20M" > /sys/fs/cgroup/<grp>/io.max. Mit BFQ werden Gewichte sehr präzise in Bandbreitenanteile umgesetzt – ideal für Shared-Hosting und Container-Hosts, auf denen Fairness wichtiger ist als maximale Rohleistung.

Praxisvergleich: Welche Wahl zur Hardware passt

Die Wahl hängt stark vom Speicher-Typ und der Queue-Architektur ab, daher prüfe ich zuerst Gerät und Controller. SSD und NVMe profitieren meist von Noop/none, HDDs laufen mit mq-deadline oder BFQ runder. In RAID-Setups, SANs und Allround-Hosts ziehe ich mq-deadline häufig vor, weil Deadline-Logik und Sortierung gut harmonieren. Multi-User-Umgebungen mit vielen interaktiven Sessions gewinnen oft durch BFQ. Die folgende Tabelle fasst die Stärken und sinnvollen Einsatzfelder übersichtlich zusammen:

Scheduler Hardware Stärken Schwächen Hosting-Szenarien
Noop/none SSD, NVMe, VMs Minimaler Overhead, sauberes Merging Ohne Sortierung auf HDDs nachteilig Flash-Server, Container, Hypervisor-gesteuert
mq-deadline HDD, RAID, Allround-Server Strenge Read-Priorität, Sortierung, solide Latenz Mehr Logik als Noop Datenbanken, Web-Backends, gemischte Lasten
BFQ HDD, Multi-User, Desktop-ähnliche Hosts Fairness, Reaktionsfreude, gute Sequenzen Etwas mehr Overhead auf sehr schnellen SSDs Interaktive Dienste, Shared-Hosting, Dev-Server

Konfiguration: Scheduler prüfen und dauerhaft setzen

Zuerst schaue ich, welcher Scheduler aktiv ist, etwa mit cat /sys/block/sdX/queue/scheduler, und notiere die Option in eckigen Klammern. Um temporär zu wechseln, schreibe ich zum Beispiel echo mq-deadline | sudo tee /sys/block/sdX/queue/scheduler. Für persistente Einstellungen nutze ich udev-Regeln oder Kernel-Parameter wie scsi_mod.use_blk_mq=1 und mq-deadline in der Commandline. Bei NVMe-Geräten prüfe ich Pfade unter /sys/block/nvme0n1/queue/ und setze die Wahl pro Gerät. Wichtig: Ich dokumentiere Änderungen, damit Wartung und Rollback ohne Rätselraten gelingen.

Persistenz und Automatisierung im Betrieb

Im Alltag setze ich Wiederholbarkeit über Automatisierung durch. Drei Wege haben sich bewährt:

  • udev-Regeln: Beispiel für alle HDDs (rotational=1) echo 'ACTION=="add|change", KERNEL=="sd*", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="mq-deadline"' > /etc/udev/rules.d/60-io-scheduler.rules, dann udevadm control --reload-rules && udevadm trigger.
  • systemd-tmpfiles: Für spezifische Geräte definiere ich /etc/tmpfiles.d/blk.conf mit Zeilen wie w /sys/block/sdX/queue/scheduler - - - - mq-deadline, die beim Boot schreiben.
  • Konfig-Management: In Ansible/Salt lege ich Geräteklassen (NVMe, HDD) an und verteile konsistente Defaults samt Dokumentation und Rollback.

Hinweis: elevator= als Kernel-Parameter galt für den alten Single-Queue-Pfad. In blk-mq bestimme ich die Wahl pro Gerät. Bei Stacks (dm-crypt, LVM, MD) setze ich die Vorgabe am Top-Device, dazu mehr weiter unten.

Workloads im Hosting: Muster erkennen und richtig handeln

Ich analysiere zunächst die Last: Viele kleine Reads deuten auf Webfrontends hin, Sync-heavy Writes auf Datenbanken und Log-Pipelines, große sequenzielle Streams auf Backups oder Archiv. Werkzeuge wie iostat, vmstat und blktrace zeigen Warteschlangen, Latenzen und Merge-Effekte. Bei auffälliger CPU-Leerlaufzeit durch I/O verweise ich auf I/O-Wait verstehen, um Engpässe strukturiert zu beheben. Danach teste ich 1–2 Scheduler-Kandidaten in identischen Zeitfenstern. Erst Messergebnisse entscheiden, nicht Bauchgefühl oder Mythen.

Messpraxis vertiefen: reproduzierbare Benchmarks

Für belastbare Entscheidungen nutze ich kontrollierte fio-Profile und bestätige mit echten Applikations-Tests:

  • Zufalls-Reads (Web/Cache): fio --name=rr --rw=randread --bs=4k --iodepth=32 --numjobs=4 --runtime=120 --time_based --filename=/mnt/testfile --direct=1
  • Zufalls-Mix (DB): fio --name=randmix --rw=randrw --rwmixread=70 --bs=8k --iodepth=64 --numjobs=8 --runtime=180 --time_based --direct=1
  • Sequenziell (Backup): fio --name=seqw --rw=write --bs=1m --iodepth=128 --numjobs=2 --runtime=120 --time_based --direct=1

Parallel logge ich iostat -x 1, pidstat -d 1 und notiere P95/P99-Latenzen aus fio. Für Tiefendiagnosen setze ich blktrace oder eBPF-Tools wie biolatency ein. Wichtig: Ich messe zu gleichen Tageszeiten, gleiche Lastfenster, gleiche Dateigrößen. Cache-Effekte minimiere ich mit direct=1 und sauberen Pre-Conditions (z. B. Pre-Fill auf dem Volume).

Dateisysteme und I/O Scheduler: Zusammenspiel zählt

Das Dateisystem beeinflusst die I/O-Charakteristik, daher prüfe ich dessen Journal-Modus, Queue-Tiefe und Sync-Verhalten sehr genau. EXT4 und XFS arbeiten effizient mit mq-deadline, während ZFS vieles selbst puffert und aggregiert. Auf Hosts mit ZFS beobachte ich den Scheduler-Effekt häufig geringer, weil ZFS die Ausgabe bereits formt. Für Vergleiche nutze ich identische Mount-Optionen und Workloads. Wer Optionen abwägt, findet in EXT4, XFS oder ZFS hilfreiche Perspektiven auf Storage-Tuning.

Writeback, Cache und Barrieren: die oft übersehene Hälfte

Scheduler können nur so gut wirken, wie es das Writeback-Subsystem erlaubt. Ich prüfe daher stets:

  • dirty-Parameter: sysctl vm.dirty_background_bytes, vm.dirty_bytes, vm.dirty_expire_centisecs steuern, wann und wie aggressiv der Kernel schreibt. Für Datenbanken senke ich oft Burst-Spitzen, um P99 stabil zu halten.
  • Barrieren/Flush: Optionen wie EXT4 barrier bzw. XFS Default-Flushes sichere ich nur ab, wenn Hardware (z. B. BBWC) sie übernimmt. „nobarrier“ ohne Stromschutz ist riskant.
  • Device Write-Cache: Ich verifiziere Write-Cache-Einstellungen des Controllers, damit fsync wirklich auf dem Medium landet und nicht nur im Cache.

Wer Writeback glättet, entlastet den Scheduler – Deadlines bleiben verlässlich, und BFQ muss weniger gegen plötzliche Flush-Wellen anarbeiten.

Virtualisierung, Container und Cloud: Wer plant wirklich?

In VMs steuert der Hypervisor den physischen I/O-Fluss, weshalb ich im Gast oft Noop/none wähle, um doppelte Logik zu vermeiden. Auf dem Host selbst nutze ich mq-deadline oder BFQ je nach Gerät und Aufgabe. Bei Cloud-Volumes (z. B. Netzwerk-Block-Storage) liegen Teile der Planung im Backend; daher messe ich reale Latenzen statt auf Annahmen zu vertrauen. Für Container-Hosts mit stark gemischter Last bringt BFQ häufig bessere Interaktivität. In homogenen Batch-Clustern mit Flash-Only setzt sich Noop durch, weil jede CPU-Zeit zählt und Controller effizient arbeiten.

RAID, LVM, MD und Multipath: wo der Scheduler greift

In gestapelten Block-Stacks setze ich den Scheduler am Top-Device an, denn dort liegen die relevanten Queues:

  • LVM/dm-crypt: Scheduler am /dev/dm-* bzw. /dev/mapper/<lv> setzen. Die physischen PVs lasse ich meist auf none, damit Merging/Sortierung nicht doppelt passiert.
  • MD-RAID: Am /dev/mdX entscheiden; darunterliegende sdX Geräte bleiben unaufgeregt auf none. Hardware-RAID wird wie ein einzelnes Blockdevice behandelt.
  • Multipath: Am Multipath-Mapper (/dev/mapper/mpatha) festlegen; Pfad-Devices darunter auf none.

Wichtig: Ich trenne Tests nach Pool und Redundanz-Level (RAID1/10 vs. RAID5/6). Paritäts-RAIDs reagieren empfindlicher auf Random-Writes; hier gewinnt mq-deadline oft durch konsequente Read-Deadlines und geordnete Ausgabe.

Tuning-Strategien: Schritt für Schritt zur verlässlichen Leistung

Ich starte mit einer Basis-Messung: aktuelle Antwortzeiten, Durchsatz, 95./99.-Perzentile und CPU-Load. Danach ändere ich nur einen Faktor, typischerweise den Scheduler, und wiederhole dieselbe Last. Tools wie fio helfen kontrolliert, doch ich bestätige jede Hypothese mit realen Applikations-Tests. Für Datenbanken eignen sich eigene Benchmarks, die Transaktionen und fsync-Verhalten abbilden. Erst wenn die Messung stabil ist, schreibe ich die Wahl fest und dokumentiere das Warum.

Queue-Tiefe, Readahead und CPU-Affinität

Neben dem Scheduler beeinflussen Queue-Parameter die Praxis stark:

  • Queue-Tiefe: /sys/block/<dev>/queue/nr_requests limitiert Pending-Requests pro Hardware-Queue. NVMe verträgt hohe Tiefe (hoher Durchsatz), HDDs profitieren von moderater Tiefe (stabilere Latenz).
  • Readahead: /sys/block/<dev>/queue/read_ahead_kb bzw. blockdev --getra/setra. Für sequenzielle Workloads etwas höher, für Random niedrig halten.
  • rq_affinity: Mit /sys/block/<dev>/queue/rq_affinity auf 2 sorge ich dafür, dass I/O-Completion bevorzugt auf dem erzeugenden CPU-Core landet – das reduziert Cross-CPU-Kosten.
  • rotational: Ich verifiziere, dass SSDs rotational=0 melden, damit der Kernel keine HDD-Heuristiken anwendet.
  • Merges: /sys/block/<dev>/queue/nomerges kann Merges reduzieren (2=aus). Für NVMe-Mikrolatenz teils sinnvoll, für HDDs meist nachteilig.
  • io_poll (NVMe): Polling kann Latenzen senken, braucht aber CPU. Ich aktiviere es gezielt bei Low-Latency-Anforderungen.

Scheduler-Tunables im Detail

Je nach Scheduler stehen sinnvolle Feinschrauben bereit:

  • mq-deadline: /sys/block/<dev>/queue/iosched/read_expire (ms, typisch klein), write_expire (größer), fifo_batch (Batch-Größe), front_merges (0/1). Ich halte read_expire kurz, um P95-Reads zu schützen, und justiere fifo_batch je nach Gerät.
  • BFQ: slice_idle (Idle-Zeit zur Sequenznutzung), low_latency (0/1) für reaktionsfreudige Interaktivität. Mit bfq.weight in Cgroups steuere ich relative Anteile sehr fein.
  • none/noop: Kaum Stellschrauben, aber die Umgebung (Queue-Tiefe, Readahead) entscheidet über die Resultate.

Ich ändere immer nur einen Parameter und halte die Änderung strikt fest – so bleibt klar, was welchen Effekt hatte.

Häufige Fallstricke und wie ich sie vermeide

Gemischte Pools aus HDD und SSD hinter einem RAID-Controller verfälschen Tests, daher trenne ich Messungen pro Gruppe. Ich vergesse nicht, dass der Scheduler pro Blockgerät gilt – LVM-Mapper und MD-Devices betrachte ich getrennt. Persistenz rutscht gern durch: Ohne udev-Regel oder Kernel-Parameter steht nach Reboot wieder der Default. Cgroups und I/O-Prioritäten bleiben oft ungenutzt, obwohl sie Fairness deutlich schärfen. Und ich prüfe stets Queue-Tiefe, Writeback und Filesystem-Optionen, damit die gewählte Logik ihr Potenzial zeigt.

Troubleshooting: Symptome gezielt lesen

Wenn die Messwerte kippen, deute ich Muster und leite konkrete Schritte ab:

  • Hohe P99-Latenz bei vielen Reads: Prüfen, ob Writes Reads verdrängen. Mit mq-deadline testen, read_expire senken, Writeback glätten (vm.dirty_* anpassen).
  • 100% util auf HDD, niedriger Durchsatz: Seeks dominieren. BFQ oder mq-deadline probieren, Readahead reduzieren, Queue-Tiefe mäßigen.
  • Gute Durchsatzwerte, aber UI ruckelt: Interaktivität leidet. BFQ aktivieren, kritische Dienste per ionice -c1 oder Cgroup-Gewichte bevorzugen.
  • Starke Varianz je nach Tageszeit: Geteilte Ressourcen. Mit Cgroups isolieren, Scheduler je Pool wählen, Backups auf Off-Peak verschieben.
  • NVMe-Timeouts im dmesg: Backend oder Firmware-Thema. io_poll testweise deaktivieren, Firmware/Driver prüfen, Pfad-Redundanz (Multipath) verifizieren.

Kurz zusammengefasst: Klare Entscheidungen für Hosting-Alltag

Für Flash-Storage und Gäste entscheide ich mich häufig für Noop, um Overhead zu sparen und Controller arbeiten zu lassen. In Allround-Servern mit HDD oder RAID liefert mq-deadline verlässliche Latenz und hohe Nutzbarkeit. Bei vielen aktiven Nutzern und interaktiver Last sorgt BFQ für faire Anteile und spürbare Reaktionsfreude. Ich messe vor jeder Festschreibung mit realen Workloads und beobachte die Effekte auf P95/P99. So treffe ich nachvollziehbare Entscheidungen, halte Systeme flott und stabilisiere die Server-Performance im Tagesgeschäft.

Aktuelle Artikel