Download - MySQL InnoDB Checkpoint Blues
Checkpoint BluesDie wunderbare Welt von Isotopp
Montag, 19. September 2011Checkpoint BluesWer dies und dies gelesen hat, versteht mehr.
InnoDB ist eine Storage Engine, die mit Hilfe von MVCC Transaktionen implementiert. Transaktionen zuimplementieren bedeutet, daß man in der Lage ist, mehrere Änderungen zusammenzufassen und alseine Einheit als gültig zu markieren oder zurück zu nehmen. Damit das Ganze trotzdem schnell ist, mußman ein wenig herumtricksen.
Angenommen, wir wollen eine Spalte in einer Zeile in der Tabelle t ändern:
UPDATE t SET x=17 WHERE id=3
Dann muß InnoDB das zunächst einmal in eine Zeilennummer in einer Speicherseite übersetzen: InnoDBspeichert Daten in Seiten von 16 KB (Defaultgröße) ab, und macht allen I/O in Richtung Tablespaceimmer nur in ganzen Seiten. In unserem Beispiel bedeutet das also, daß wir die Seite p lokalisierenmüssen, in der die ID=3 der Tabelle t gespeichert ist, und diese ganze Seite in den Speicher laden.
Laden der benötigen Seite in den Speicher. Aufbau der Transaktion im Log Buffer undÄndern der Seite im Speicher.
InnoDB lädt die Seite also aus dem TablespaceFile in den InnoDB Buffer Pool, und baut im Speicher dieTransaktion auf. Dazu wird im Log Buffer die Transaktion gebastelt und parallel dazu die Speicherseite imInnoDB Buffer Pool angepaßt. Die alte Version der betroffenen Zeile wird außerdem in das Undo Logverschoben (dies ist nicht mit eingezeichnet, weil es in diesem Text nicht darum gehen soll). Diegeänderte Speicherseite im RAM wird nicht zurück geschrieben der Inhalt der Seite im RAM und auf derPlatte divergieren, die Seite ist DIRTY (hier: rot markiert).
Beim COMMIT wird der Log Buffer in das RedoLog geschrieben. Es besteht eine
Verknüpfung zwischen der geänderten Speicherseite und der Transaktion im Redo
Log.
Beim COMMIT wird der Log Buffer ins Redo Log geschrieben. Die geänderte Speicherseite (rot: DIRTY)wird immer noch nicht zurück geschrieben.
Hätten wir stattdessen ein ROLLBACK durchgeführt, hätte InnoDB die alte Version der Zeile aus demUndoLog geprökelt und die Änderung zurück genommen es wäre niemals zu einem Schreibzugriff imRedoLog gekommen, und die DIRTY Page im Speicher wäre wieder CLEAN.
Zwischen dem Eintrag im RedoLog und der als DIRTY markierten Speicherseite besteht eineVerbindung, denn irgendwann einmal muß die Seite ja doch geschrieben werden. InnoDB versucht dasaber so gut es geht zu verzögern: Schreiben in das RedoLog ist viel schneller als Schreiben in denTablespace.
Zum einen werden im RedoLog nur die Änderungen notiert, nicht ganze Speicherseiten. Daher ist dasVolumen der Daten im RedoLog pro Transaktion meistens kleiner als das Volumen der als DIRTYmarkierten Pages.
Und anderen ist es so, daß das RedoLog ungefähr das Erste ist, was eine neue MySQLInstanz bei derInbetriebnahme als Dateien anlegt. Die Dateien sind also, wenn man alles richtig gemacht hat, in einemleeren oder fast leeren Dateisystem angelegt worden und nur sehr wenig fragmentiert. Da sie alsRingpuffer benutzt werden und niemals neu angelegt werden, bleibt diese Eigenschaft stabil über dieLebensdauer der Datenbank erhalten.
Schließlich ist es so, daß die Daten in InnoDB ja in der Reihenfolge der Primärschlüsselwerteabgespeichert werden. Wählt man diesen geschickt, dann ist es so, daß häufig zusammenangesprochene Daten auch physikalisch zusammen auf derselben InnoDB Speicherseite stehen. Indiesem Fall ist es wahrscheinlich, daß eine bereits als DIRTY markierte Page noch ein weiteres Mal innaher Zukunft geändert wird. Hätte man die Änderung bereits zurück geschrieben, wäre die Seite wiederCLEAN und würde gleich wieder als DIRTY markiert werden, um dann wiederum neu geschriebenwerden zu müssen.
Das Schreiben ins RedoLog sichert also die Minimalmenge an Daten in einer Datei, die für linearesSchreiben optimiert ist, und verkleinert die zu schreibende Datenmenge, indem uns ermöglicht wird, dasZurückschreiben von ganzen Speicherseiten zu verzögern und Änderungen zu aggregieren, ohne die DEigenschaft (Durability) von ACID zu verlieren.
Beim Checkpoint werden aus den ältesten RedoLog einträgen Speicherseiten bestimmt,die zurück geschrieben werden.
Irgendwann einmal müssen wir aber auch Speicherseiten zurück schreiben. InnoDB guckt sich dabei dieältesten Einträge im RedoLog an und sucht die von diesen Einträgen referenzierten Speicherseiten.Sobald eine gewisse Menge (1MB, 64 Seiten) an Seiten gefunden ist, werden diese in den Tablespacezurück geschrieben. Dies bezeichnet man als Checkpoint.
Der Checkpoint setzt also DIRTY Pages auf CLEAN und gibt zugleich wieder Speicher im RedoLog derDatenbank frei.
Dabei schreibt InnoDB Speicherseiten in zwei Schritten zurück: Eine einzelne Seite (16 KB) ist größer alsein Plattenblock. Wenn also das System mitten beim Schreiben einer Seite stehen bleibt, könnte esvorkommen, daß eine Seite zur Hälfte auf dem neuen und zur Hälfte auf dem alten Stand ist. InnoDBverhindert das, indem ein Satz von 64 Speicherseiten zunächt einmal in den DoublewriteBuffer (nichteingezeichnet) geschrieben wird, das RedoLog als frei markiert wird und dann dieselben Seiten erst anOrt und Stelle geschrieben werden.
Bei einem Crash mitten im Schreiben in den DoublewriteBuffer ist nichts verloren: Das System kann dieungeänderten Seiten aus dem Tablespace angeln, das RedoLog anwenden, um diese im Speicher aufden neuen Stand zu patchen und dann ganz normal checkpointen.
Bei einem Crash mittem im Schreiben in den Tablespace ist ebenfalls nichts verloren: Das System fischtdie neuen Versionen der Pages aus dem DoublewriteBuffer und kopiert diese an die richtigen Stellen imTablespace um.
Drucksituationen
Die Datenbank führt normalerweise einen Checkpoint aus, wenn sie sich langweilt: Ist InnoDB einige Zeitidle, wird irgendein Thread im System sich des RedoLogs annehmen und die DIRTY Pages der Reihenach lustig wegcheckpointen. Nach einer gewissen Zeit hat man 0 RedoLog und 0 DIRTY Pages.
Anteil des genutzten RedoLog in einem MySQL 5.1 mit InnoDBPlugin.
Wenn das fragliche System jedoch sehr beschäftigt ist und über eine längere Zeit eine kontinuierliche
Schreiblast sieht, dann kann es passieren, daß gar nicht genug IdleZeiten vorhanden sind, um über
diesen Prozeß Daten weg zu checkpointen. Im Bild oben sieht man eine InnoDBInstanz mit Schreiblast
und einem viel zu kleinen ib_logfile{0,1} von nur 2 x 128 MB. Nach einer Nachtpause wird das System
wieder aktiv und bekommt genug Schreiblast, um im RedoLog eine Art Backlog aufzubauen. Ab kurz
nach 8 Uhr morgens ist die Last so groß, daß das genutzte RedoLog fest auf ca. 50% Nutzung steht und
das System kontinuierlich gezwungen ist, Daten nach hinten raus zu checkpointen.
Menge der DIRTYPages derselben Maschine.
In einem anderen Graphen sieht man die Menge der DIRTYPages derselben Maschine die
Gesamtgröße des innodb_buffer_pool dieser Box ist 40 GB, der Anteil der DirtyPages ist also auch zu
Spitzenzeiten kaum höher als 5%. Da das RedoLog jedoch vergleichsweise klein ist, geht von dort
ausreichend Schreibdruck aus, um Checkpoints zu erzwingen.
Es ist auch erkennbar, daß die Größen von RedoLog und DIRTYPages nicht eng gekoppelt miteinander
korrellieren: Zählt man in einer Schleife immer wieder denselben Zähler hoch ("UPDATE x SET z=z+1
WHERE id=1"), dann erzeugt man jede Menge RedoLog, aber arbeitet immer auf derselben einen
DIRTY Page rum. Andererseits kann man eine ID zufällig auswählen und dann nur ein einziges Bit in
dieser Zeile ändern dies würde eine zufällige, ganze 16 KB Page als DIRTY markieren, aber kaum
RedoLog erzeugen.
Checkpoint Blues
Checkpointing bei InnoDB korrekt hin zu bekommen ist frustrierend schwierig: Die Größe des Logfiles ist
bei InnoDB mehr oder weniger unveränderlich über die Lebensdauer der Installation. Andererseitsmöchte man das Checkpointen so lange als möglich herauszögern, damit man keine unnötige Schreiblast
erzeugt (gerade geschriebene Pages werden durch weitere Änderungen wieder als DIRTY markiert).
Der CheckpointingAlgorithmus von InnoDB spielt also 17+4: Zögert er zu lange, läuft das RedoLog voll,
oder es sind keine Seiten im Buffer Pool mehr vorhanden, die nicht als DIRTY markiert sind. Dann muß
das System einen Checkpoint erzwingen und hält alle Schreibzugriffe notgedrungen an, bis wieder Platz
vorhanden ist. Das will man um jeden Preis vermeiden. Checkpointed der Algorithmus aber zu aggressiv,
kommt es zu einer erhöhten Schreiblast im Plattensubsystem und die Datenbank kann nicht mit
maximaler Performance schreiben.
Der perfekte Algorithmus müßte in die Zukunft schauen können: Er könnte anhand des noch
vorhandenen Platzes, der Schreibkapazität der Spindeln und der kommenden Schreiblast maximal spät
mit dem Schreiben anfangen.
Das ist offensichtlich unmöglich, und nicht perfekte Algorithmen verhalten sich im Benchmark etwa wie
dieses MySQL 5.5: In einer Maschine mit einem großen Buffer Pool (72G) und einem großen Logfile(3.8G) kommt es immer wieder zu Schreibstürmen, während derer die Datenbank mit dem Checkpointen
so beschäftigt ist, daß sie keine oder kaum noch Kommandos ausführt im Beispiel schafft es der Tester,
die Datenbank wiederholt für Perioden von 4 Minuten lahm zu legen.
Zum Vergleich: Postgres
Postgres hat dasselbe Problem zu lösen: Auch hier haben wir eine Datenbank mit MVCC, die
Transaktionen loggt und als DIRTY markierte Pages im Speicher zu halten versucht. Drei Dinge sind
grundsätzlich anders:
Zum Ersten hat Postgres kein UndoLog: Alte und neue Versionen einer Zeile stehen in der Tabelle
selbst. Diese "versandet" im Laufe der Zeit immer mehr, d.h. alte Versionen einer bestimmten Zeile
blähen die Tabelle auf ein mehrfaches der minimal notwendigen Größe auf, im Index und in den Daten.
Postgres kompaktiert Tabellen durch einen im Hintergrund laufenden Prozeß namen Vacuum, der alle
Tabellen reihum durchgeht und veraltete Daten löscht.
Zum Zweiten hat Postgres keinen ausdrücklichen DoublewriteBuffer. Stattdessen wird im RedoLog
(Postgres nennt es WAL: WriteAheadLog) eine Mixtur von ganzen Seiten und geänderten Zeilen
abgelegt. Wenn eine Page vom Zustand CLEAN in den Zustand DIRTY wechselt, dann schreibt Postgres
die ganze, bei Postgres nur 8 KB große Seite ins WAL. Wenn eine DIRTY Page weiter geändert wird,
dann wird, wie bei InnoDB auch, nur die Änderungsinformation für die Zeile ins WAL geschrieben.
Stirbt Postgres im Checkpoint beim Schreiben eines Blocks im Tablespace, dann kann die alte Version
des Blocks aus dem WAL gepult werden und mit den weiteren, ebenfalls dort geloggten Änderungen auf
Stand gebracht werden.
Und zum Dritten ist das WAL bei Postgres nicht in seiner Größe begrenzt, sondern wird als fortlaufende
Folge von numerierten WALFiles analog zum MySQLBinlog geschrieben. Unter Druck kann es wie bei
InnoDB vorkommen, daß die Datenbank mit dem Checkpointen von DIRTYPages nicht hinterher kommt
der Anteil an WALFiles, der in einer Recovery nach eine Crash benötigt werden würde, wächst so
immer weiter über alle Grenzen hinaus. Aber dafür kann es nicht vorkommen, daß die Datenbank sie
Ausführung von Kommandos verzögern muß, um Platz in ihren Logs zu schaffen im Falle eines
Crashes gibt es jedoch keine obere Grenze für die RecoveryZeit, und für die WALDateien gibt es keine
FragmentierungsGarantien (das läßt sich durch das Führen der WALDateien in einem separaten
Dateisystem aber unter Kontrolle bringen).
Die CheckpointingStrategien von Postgres und InnoDB arbeiten also unter grundsätzlich anderen
Vorraussetzungen und sind nicht leicht vergleichbar.
Geschrieben von Kristian Köhntopp in MySQL um 20:00 | Kommentare (3) | Trackbacks (0)
TrackbacksTrackbackURL für diesen Eintrag
Keine Trackbacks
KommentareAnsicht der Kommentare: (Linear | Verschachtelt)
Weil es gefragt wurde: innodb_io_capacity#1 Kristian Köhntopp (Link) am 19.09.2011 20:40
Vielen Dank für den interessanten Artikel. Könntest Du vielleicht noch ein paar Worte im Vergleich zuOracle verlieren?#2 Dieter Mosbach am 22.09.2011 10:44
Leider nein. Aber es lesen bestimmt ein paar ExKollegen mit, die jetzt bei Oracle arbeiten und hier wasschreiben oder schreiben lassen könnten.#2.1 Kristian Köhntopp (Link) am 22.09.2011 12:42
Kommentar schreibenName
EMail
Homepage
Antwort zu
Kommentar
Umschließende Sterne heben ein Wort hervor (*wort*), per _wort_ kann ein Wortunterstrichen werden.
Um maschinelle und automatische Übertragung von Spamkommentaren zu verhindern, bittedie Zeichenfolge im dargestellten Bild in der Eingabemaske eintragen. Nur wenn dieZeichenfolge richtig eingegeben wurde, kann der Kommentar angenommen werden. Bittebeachten Sie, dass Ihr Browser Cookies unterstützen muss, um dieses Verfahrenanzuwenden.
Hier die Zeichenfolge der SpamschutzGrafik eintragen:
BBCodeFormatierung erlaubt Daten merken?