Ubuntu ist für mich das Arbeitstier schlechthin – mit keinem anderen Betriebssystem würde ich derzeit längere Zeit arbeiten wollen. Auch wenn ein kleiner Blick gen OpenIndiana schweift – an OpenSolaris habe ich immer bewundert, wie gut sich das System unter Last noch anfühlt. Genau das vermisse ich bei Ubuntu: Wenn ein Maven-Build läuft, Netbeans meint es müsse mal wieder tüchtig scannen oder ich dabei noch ein paar Kleinigkeiten machen will, wird es schnell mal duster. Im wahrsten Sinne des Wortes: Applikationen, die durch IO blockiert sind, bekommen dunklere Fenster auf dem Desktop.

Und das ist nicht gerade selten der Fall, bestes Beispiel ist Firefox wenn ich auf eine Seite gehe, auf der 20 verschiedene Flash Applets eingebunden sind (z.B. Google Adsense Review Center, also keine Game Seiten). 10 Sekunden Bedenkpause mindestens. Von virtualisierten Maschinen die parallel laufen mal ganz zu schweigen – diese haben eine eigene Festplatte spendiert bekommen.

Nun hat es mich also interessiert, wie kann es sein, dass bei gleicher Hardware ein Solaris so viel flotter von der Hand geht? Sicherlich hat der Scheduler was damit zu tun, und vielleicht sind die Treiber auch optimiert – aber das kann nicht alles sein. Richtig: Denn es gibt nicht nur Scheduler für die CPU Zeit, sondern auch für die IO Zugriffe. Alle Lese- und Schreiboperationen werden von dem Betriebssystem nach einem bestimmten Prinzip abgearbeitet. Der einfachste Scheduler tut einfach nichts („noop“) und reicht die Anfragen direkt an die Hardware weiter. Das macht z.B. bei SSD Speichern und virtuellen Maschinen Sinn: Bei SSD Festplatten zahlt man keinen direkten Strafzoll in Form von langsamen Kopfbewegungen bei der Festplatte für weit entfernt liegende Datenblöcke, und virtuelle Maschinen greifen sowieso nicht direkt auf die Hardware zu.

Bei normalen Festplatten lohnt es sich stets eine kurze Zeitspanne zu warten, um ggf. weitere Blöcke aus der Nähe einer Anfrage zu Bearbeiten, ehe man einen – vergleichsweise teuren – „seek“ mit dem Schreib-Lese-Kopf macht. Schnell stellt sich die Frage, wie lange man da warten soll, und auch welcher Prozess welche Priorität bei Anfragen bekommen sollte.

Seit Linux 2.6.6 wurde der CFQ Scheduler, „completely fair queuing“, in den Kernel aufgenommen und seit dem immer weiter optimiert. Seit Linux 2.6.18 ist CFQ der Standard Scheduler für IO.

Die Grundidee von CFQ ist, dass jeder Prozess seine eigene Warteschlange bekommt. Dann wird jeder Warteschlange entsprechend der IO Priorität des Prozesses ein Zeitfenster eingeräumt, um die Anfragen abzuarbeiten. Die IO Priorität hängt dabei u.a. von der Priorität des Prozesses und  dem bisherigen Ressourcenverbrauch des Prozesses ab. Dieser Scheduler optimiert damit den IO Durchsatz sehr gut, auch bei Systemen mit vielen aktiven Prozessen. Jedoch geht der hohe Durchsatz manchmal auf Kosten der Latenz und ist damit eher für Serverumgebungen als für den Desktopeinsatz geeignet: Wenn Firefox Daten anfragt, weil ich es ihm befohlen habe, ist es mir wichtiger dass diese vor einem danach gestartetem Logeintrag geliefert werden.

Und genau hier kommt der „Deadline“ Scheduler zum Einsatz: Jeder IO Anfrage wird hier ein Zeitstempel zugewiesen wann dieser IO-Request spätestens zu bearbeiten ist. Danach werden die Anfragen in einer Warteschlange sortiert – eine zweite Warteschlange ist sortiert nach den angefragten Datensektoren. Solange keine Anfrage ihre Lebenszeit überschreitet, werden aus der nach Sektoren sortierten Warteschlange alle Anfragen abgearbeitet und damit ein hoher Durchsatz erzielt.
Zusätzlich werden bei jedem Lesevorgang auch benachbarte Sektoren „auf Verdacht“ geladen – für normale Festplatte eine fast kostenneutrale Operation, da der Kopf sowieso sehr nahe bei den angefragten Sektoren ist. Bei SSD Platten ist es sogar noch günstiger, wenn der Sektor im selben Datenblock ist, da SSD Speicher immer ganze Blöcke lesen müssen, im seltenen Fall einer Blockgrenze entstehen etwas höhere Kosten da der naechste Block gelesen werden muss.

Ist die Lebenszeit einer Anfrage abgelaufen, so wird diese sofort bearbeitet und passende weitere Anfragen aus der nach Sektoren sortierten Warteschlange mit abgearbeitet. Damit wird die Latenz gesenkt, was möglicherweise auf Kosten des Durchsatzes geht, jedoch verhindert, dass Prozesse „verhungern“.
Standardmäßig werden Leseoperationen mit einer Deadline von 500ms versehen, Schreiboperationen bekommen 5 Sekunden als Lebenszeit.

Ich habe nun den bei Ubuntu Linux ebenfalls als Standard eingestellten CFQ (completely fair queue) Scheduler testweise gegen den „Deadline“ Scheduler ausgetauscht. Das geht sehr einfach auf verschiedene Weise:

  1. Beim Booten in Grub die Option „elevator=deadline“ setzen, so wird für alle Blockgeraete der Deadline Scheduler verwendet
  2. Nach dem Booten fuer das Blockdevice „sda“ den Deadline-Scheduler setzen:
    echo „deadline“ | sudo tee /sys/block/sda/queue/scheduler
  3. Dauerhaft: Entweder in /etc/rc.local entsprechende Eintraege machen, oder die Grub Optionen umkonfigurieren

Meine Erfahrungen: Die Festplatte klappert etwas lauter und hörbar „anders“ als mit CFQ, der Bootvorgang dauert gefühlt einen Hauch länger, aber das „dunkles Fenster“ Syndrom hört auf. Alle Anwendungen reagieren etwas flotter, von einem möglichen Durchsatzeinbruch merke ich höchstens beim Booten etwas.

Ich werde bis zum „noop“ Scheduler mit einer SSD also beim Deadline bleiben. Vielleicht gibt es dazu demnächst ein Update im Blog – wenn ich mich doch um entscheide.