Datenkomprimierung in Apache Ignite. Sberbank Erfahrung

Bei der Arbeit mit großen Datenmengen kann das Problem des unzureichenden Speicherplatzes manchmal akut werden. Eine Möglichkeit, dieses Problem zu lösen, ist die Komprimierung, aufgrund derer Sie es sich auf demselben Gerät leisten können, das Speichervolumen zu erhöhen. In diesem Artikel werden wir uns ansehen, wie die Datenkomprimierung in Apache Ignite funktioniert. In diesem Artikel werden nur die im Produkt implementierten Methoden zur Festplattenkomprimierung beschrieben. Andere Methoden der Datenkomprimierung (über das Netzwerk, im Speicher), sowohl implementiert als auch nicht, bleiben außerhalb des Bereichs.

Wenn der Persistenzmodus aktiviert ist, beginnt Ignite aufgrund der Änderung von Daten in Caches mit dem Schreiben auf die Festplatte:

  1. Cache-Inhalt
  2. Write Ahead Log (im Folgenden als WAL bezeichnet)

Ein Mechanismus namens WAL-Verdichtung existiert seit langem, um WALs zu komprimieren. Mit dem kürzlich veröffentlichten Apache Ignite 2.8 wurden zwei weitere Mechanismen zum Komprimieren von Daten auf der Festplatte eingeführt: die Komprimierung von Festplattenseiten zum Komprimieren des Inhalts von Caches und die Komprimierung von WAL-Seiten-Snapshots zum Komprimieren einiger WAL-Datensätze. Mehr zu all diesen drei Mechanismen weiter unten.

Komprimierung der Festplattenseite


Wie es funktioniert


Zunächst werden wir kurz darauf eingehen, wie Ignite Daten speichert. Für die Speicherung wird der Seitenspeicher verwendet. Die Seitengröße wird am Anfang des Knotens festgelegt und kann zu einem späteren Zeitpunkt nicht mehr geändert werden. Außerdem muss die Seitengröße eine Zweierpotenz und ein Vielfaches der Größe des Dateisystemblocks sein. Seiten werden nach Bedarf von der Festplatte in den RAM geladen. Die Datengröße auf der Festplatte kann die Menge des zugewiesenen RAM überschreiten. Wenn im RAM nicht genügend Speicherplatz zum Laden von Seiten von der Festplatte vorhanden ist, werden alte, nicht verwendete Seiten aus dem RAM verdrängt.

Die Daten werden in der folgenden Form auf der Festplatte gespeichert: Für jede Partition jeder Cache-Gruppe wird eine separate Datei erstellt. In dieser Datei werden die Seiten in aufsteigender Reihenfolge des Index nacheinander verschoben. Die vollständige Seitenkennung enthält die Cache-Gruppenkennung, die Partitionsnummer und den Seitenindex in der Datei. Somit können wir anhand der vollständigen Seitenkennung die Datei und den Versatz in der Datei für jede Seite eindeutig identifizieren. Weitere Informationen zum Seitenspeicher finden Sie in einem Artikel im Apache Ignite Wiki: Ignite Persistent Store - unter der Haube .

Der Mechanismus zur Komprimierung von Festplattenseiten funktioniert, wie der Name schon sagt, auf Seitenebene. Wenn dieser Mechanismus aktiviert ist, wird die Arbeit mit Daten im RAM unverändert ohne Komprimierung ausgeführt. Zum Zeitpunkt des Speicherns von Seiten vom RAM auf die Festplatte werden sie jedoch komprimiert.

Das Komprimieren jeder Seite einzeln ist jedoch keine Lösung für das Problem. Sie müssen die Größe der resultierenden Datendateien irgendwie reduzieren. Wenn die Seitengröße nicht mehr festgelegt wird, können wir keine Seiten mehr einzeln in eine Datei schreiben, da dies zu einer Reihe von Problemen führen kann:

  • Wir können den Seitenindex nicht verwenden, um den Versatz zu berechnen, an dem er sich in der Datei befindet.
  • , , . , . , .
  • , , , .

Um diese Probleme nicht auf eigener Ebene zu lösen, verwendet die Komprimierung von Festplattenseiten in Apache Ignite einen Dateisystemmechanismus namens Sparse-Dateien. Eine Sparse-Datei ist eine Datei, in der einige mit Nullen gefüllte Bereiche als Löcher markiert werden können. In diesem Fall werden Blöcke des Dateisystems zum Speichern dieser Löcher nicht zugewiesen, wodurch Speicherplatz gespart wird.

Es ist logisch, dass zum Freigeben des Dateisystemblocks die Lochgröße größer oder gleich dem Dateisystemblock sein muss, wodurch die Seitengröße von Apache Ignite zusätzlich eingeschränkt wird: Damit die Komprimierung zumindest einen gewissen Effekt erzielt, muss die Seitengröße streng größer als die Dateisystemblockgröße sein . Wenn die Seitengröße der Größe des Blocks entspricht, können wir niemals einen einzelnen Block freigeben, da zum Freigeben eines einzelnen Blocks eine komprimierte Seite erforderlich ist, die 0 Byte belegt. Wenn die Seitengröße der Größe von 2 oder 4 Blöcken entspricht, können wir bereits mindestens einen Block freigeben, wenn unsere Seite auf mindestens 50% bzw. 75% komprimiert ist.

Daher die endgültige Beschreibung des Mechanismus: Beim Schreiben einer Seite auf die Festplatte wird versucht, die Seite zu komprimieren. Wenn die Größe der komprimierten Seite die Freigabe eines oder mehrerer Blöcke des Dateisystems ermöglicht, wird die Seite in komprimierter Form geschrieben und anstelle der freigegebenen Blöcke ein "Loch" gestanzt (ein Systemaufruf fallocate()mit dem Flag "Lochloch" wird ausgeführt). Wenn die Größe der komprimierten Seite keine Freigabe von Blöcken zulässt, wird die Seite unverändert in unkomprimierter Form gespeichert. Alle Seitenversätze werden ebenso berücksichtigt wie ohne Komprimierung, indem der Seitenindex mit der Seitengröße multipliziert wird. Es ist keine Selbstverlagerung von Seiten erforderlich. Seitenversätze sowie ohne Komprimierung fallen an die Grenzen von Dateisystemblöcken.



In der aktuellen Implementierung kann Ignite nur mit spärlichen Dateien unter Linux arbeiten, sodass die Komprimierung von Festplattenseiten nur aktiviert werden kann, wenn Ignite unter diesem Betriebssystem verwendet wird.

Komprimierungsalgorithmen, die für die Komprimierung von Festplattenseiten verwendet werden können: ZSTD, LZ4, Snappy. Darüber hinaus gibt es einen Betriebsmodus (SKIP_GARBAGE), in dem nur eine nicht verwendete Stelle auf der Seite verworfen wird, ohne die verbleibenden Daten zu komprimieren, wodurch die CPU im Vergleich zu den oben aufgeführten Algorithmen entlastet werden kann.

Auswirkungen auf die Leistung


Leider habe ich die Leistung an realen Ständen nicht gemessen, da wir diesen Mechanismus nicht in der Produktion einsetzen wollen, aber theoretisch können wir spekulieren, wo wir verlieren und wo wir gewinnen werden.

Dazu müssen wir uns daran erinnern, wie Seiten beim Zugriff gelesen und geschrieben werden:

  • Wenn ein Lesevorgang ausgeführt wird, wird er zuerst im RAM gesucht. Wenn die Suche fehlschlägt, wird die Seite mit demselben Stream, der liest, von der Festplatte in den RAM geladen.
  • Wenn Sie einen Schreibvorgang ausführen, wird die Seite im RAM als fehlerhaft markiert, während das physische Speichern der Seite auf der Festplatte unmittelbar im Stream, der die Aufzeichnung durchführt, nicht erfolgt. Alle verschmutzten Seiten werden später im Checkpoint-Prozess in separaten Streams auf der Festplatte gespeichert.

Somit ist die Auswirkung auf Leseoperationen:

  • (disk IO), .
  • (CPU), sparse . IO sparse ( sparse , , ).
  • (CPU), .
  • .
  • ( ):
  • (disk IO), .
  • (CPU, disk IO), sparse .
  • (CPU), .

Welche Skala wird überwiegen? Es hängt alles stark von der Umgebung ab, aber ich bin geneigt zu glauben, dass die Komprimierung von Festplattenseiten die Leistung auf den meisten Systemen eher beeinträchtigt. Darüber hinaus zeigen Tests auf anderen DBMS, die einen ähnlichen Ansatz mit Dateien mit geringer Dichte verwenden, einen Leistungsabfall, wenn die Komprimierung aktiviert ist.

So aktivieren und konfigurieren Sie


Wie oben erwähnt, die Mindestversion von Apache Ignite, die die Komprimierung von Festplattenseiten unterstützt: 2.8 und nur das Linux-Betriebssystem. Das Einschalten und Einstellen erfolgt wie folgt:

  • Der Klassenpfad muss über ein Zündkomprimierungsmodul verfügen. Standardmäßig befindet es sich in der Apache Ignite-Distribution im Verzeichnis libs / optional und ist nicht im Klassenpfad enthalten. Sie können das Verzeichnis einfach um eine Ebene nach oben in die Bibliothek verschieben. Wenn es dann über ignite.sh gestartet wird, wird es automatisch aktiviert.
  • Persistence ( DataRegionConfiguration.setPersistenceEnabled(true)).
  • ( DataStorageConfiguration.setPageSize() ).
  • , () ( CacheConfiguration.setDiskPageCompression() , CacheConfiguration.setDiskPageCompressionLevel()).

WAL compaction



Was ist WAL und warum wird es benötigt? Ganz kurz: Dies ist ein Journal, in das alle Ereignisse fallen, die sich aufgrund des Seitenrepositorys ändern. Er wird vor allem für die Möglichkeit einer Genesung im Falle eines Sturzes benötigt. Vor dem Übertragen der Kontrolle an einen Benutzer muss jeder Vorgang das Ereignis zuerst in die WAL schreiben, damit er im Falle eines Sturzes das Protokoll durchspielen und alle Vorgänge wiederherstellen kann, für die der Benutzer eine erfolgreiche Antwort erhalten hat, auch wenn diese Vorgänge keine Zeit hatten, sich im Seitenspeicher auf der Festplatte wiederzugeben (siehe oben) Es wurde beschrieben, dass das eigentliche Schreiben in den Seitenspeicher in einem Prozess ausgeführt wird, der als Prüfpunkt bezeichnet wird (mit einer gewissen Verzögerung in separaten Threads).

Einträge in der WAL sind in logische und physische Einträge unterteilt. Logische sind Schlüssel und Werte selbst. Physisch - reflektiert Seitenänderungen im Seitenspeicher. Wenn logische Datensätze in einigen anderen Fällen nützlich sein können, werden physische Datensätze nur für die Wiederherstellung im Falle eines Sturzes benötigt, und Datensätze werden erst ab dem Zeitpunkt des letzten erfolgreichen Prüfpunkts benötigt. Hier werden wir nicht auf Details eingehen und erklären, warum dies so funktioniert, aber jeder Interessierte kann auf den bereits erwähnten Artikel im Apache Ignite Wiki verweisen: Ignite Persistent Store - unter der Haube .

Ein logischer Datensatz enthält häufig mehrere physische Datensätze. Das heißt, ein Cache-Put-Vorgang wirkt sich beispielsweise auf mehrere Seiten im Seitenspeicher aus (eine Seite mit den Daten selbst, Seiten mit Indizes, Seiten mit freien Listen). Bei einigen synthetischen Tests stellte sich heraus, dass die physischen Aufzeichnungen bis zu 90% der WAL-Datei ausmachten. Darüber hinaus benötigen sie eine sehr kurze Zeit (standardmäßig beträgt das Intervall zwischen den Kontrollpunkten 3 Minuten). Es wäre logisch, diese Daten zu entfernen, nachdem sie ihre Relevanz verloren haben. Dies ist genau das, was der WAL-Komprimierungsmechanismus ausführt, physische Datensätze entfernt und die verbleibenden logischen Datensätze mit zip komprimiert, während die Dateigröße sehr stark abnimmt (manchmal zehnmal).

Physikalisch besteht eine WAL aus mehreren Segmenten (Standard 10) mit fester Größe (Standard 64 MB), die in einem Kreis überschrieben werden. Sobald das aktuelle Segment gefüllt ist, wird das nächste Segment dem aktuellen Segment zugewiesen und das gefüllte Segment in einem separaten Stream in das Archiv kopiert. Die WAL-Komprimierung funktioniert bereits mit Archivsegmenten. Außerdem überwacht es in einem separaten Stream die Ausführung des Prüfpunkts und startet die Komprimierung durch Archivsegmente, für die keine physischen Datensätze mehr benötigt werden.



Auswirkungen auf die Leistung


Da die WAL-Komprimierung als separater Thread ausgeführt wird, sollte kein direkter Einfluss auf die ausgeführten Vorgänge bestehen. Die CPU (Komprimierung) und die Festplatte (Lesen jedes WAL-Segments aus dem Archiv und Schreiben komprimierter Segmente) werden jedoch zusätzlich im Hintergrund belastet. Wenn das System also an seine Grenzen stößt, führt dies auch zu einer Verschlechterung der Leistung.

So aktivieren und konfigurieren Sie


Sie können die WAL-Komprimierung mit der Eigenschaft WalCompactionEnabledc DataStorageConfiguration (DataStorageConfiguration.setWalCompactionEnabled(true)) aktivieren . Mithilfe der DataStorageConfiguration.setWalCompactionLevel () -Methode können Sie außerdem das Komprimierungsverhältnis festlegen, wenn Sie mit dem Standardwert (BEST_SPEED) nicht zufrieden sind.

WAL-Seiten-Snapshot-Komprimierung


Wie es funktioniert


Wir haben bereits herausgefunden, dass Einträge in WAL in logische und physische Einträge unterteilt sind. Für jede Änderung jeder Seite im Seitenspeicher wird ein physischer WAL-Datensatz generiert. Physische Datensätze sind wiederum in zwei Unterarten unterteilt: Seitenschnappschussdatensatz und Delta-Datensatz. Jedes Mal, wenn wir etwas auf einer Seite ändern und es von einem sauberen in einen schmutzigen Zustand ändern, wird eine vollständige Kopie dieser Seite in der WAL (Page Snapshot Record) gespeichert. Selbst wenn wir nur ein Byte in der WAL geändert haben, wird ein Datensatz mit einer Größe gespeichert, die geringfügig größer als die Seitengröße ist. Wenn wir auf einer bereits verschmutzten Seite etwas ändern, wird in der WAL ein Delta-Datensatz gebildet, der nur die Änderungen gegenüber dem vorherigen Status der Seite widerspiegelt, nicht jedoch die gesamte Seite. Da das Zurücksetzen des Status von Seiten von Dirty auf Clean während des Checkpoint-Vorgangs durchgeführt wird,Unmittelbar nach dem Start des Prüfpunkts bestehen fast alle physischen Datensätze nur aus Schnappschüssen von Seiten (da alle Seiten unmittelbar nach dem Start des Prüfpunkts leer sind). Wenn Sie sich dem nächsten Prüfpunkt nähern, wächst der Anteil des Delta-Datensatzes und wird zu Beginn des nächsten Prüfpunkts erneut zurückgesetzt. Messungen an einigen synthetischen Tests zeigten, dass der Anteil der Seitenschnappschüsse am Gesamtvolumen der physischen Datensätze 90% erreicht.

Die Idee hinter der WAL-Seiten-Snapshot-Komprimierung besteht darin, Seiten-Snapshots mit einem handelsüblichen Seitenkomprimierungswerkzeug zu komprimieren (siehe Komprimierung von Festplattenseiten). Gleichzeitig werden Datensätze in WAL nacheinander im Nur-Anhängen-Modus gespeichert, und es besteht keine Notwendigkeit, Datensätze an die Grenzen von Dateisystemblöcken zu binden. Daher benötigen wir hier im Gegensatz zum Komprimierungsmechanismus für Festplattenseiten absolut keine Dateien mit geringer Dichte, sodass dieser Mechanismus nicht nur auf dem Betriebssystem funktioniert Linux Außerdem ist es uns egal, wie stark wir die Seite komprimieren konnten. Selbst wenn wir 1 Byte freigegeben haben, ist dies bereits ein positives Ergebnis und wir können komprimierte Daten in WAL speichern, im Gegensatz zur Komprimierung von Festplattenseiten, bei der eine komprimierte Seite nur gespeichert wird, wenn mehr als 1 Dateisystemblock freigegeben wird.

Seiten sind gut komprimierbare Daten, ihr Anteil am gesamten WAL-Volumen ist sehr hoch. Ohne Änderung des Formats der WAL-Datei können wir die Größe erheblich reduzieren. Die Komprimierung logischer Datensätze würde unter anderem eine Änderung des Formats und einen Verlust der Kompatibilität erfordern, beispielsweise für externe Verbraucher, die an logischen Datensätzen interessiert sein könnten, ohne die Dateigröße wesentlich zu verringern.

Für die Komprimierung von Festplattenseiten für die Komprimierung von WAL-Seitenschnappschüssen können die Komprimierungsalgorithmen ZSTD, LZ4, Snappy sowie der Modus SKIP_GARBAGE verwendet werden.

Auswirkungen auf die Leistung


Es ist nicht schwer zu bemerken, dass die direkte Einbeziehung der WAL-Seiten-Snapshot-Komprimierung nur die Streams betrifft, die Daten in den Seitenspeicher schreiben, dh die Streams, die die Daten in den Caches ändern. Das Lesen aus physischen WAL-Aufzeichnungen erfolgt nur einmal, zum Zeitpunkt des Anhebens des Knotens nach dem Sturz (und nur im Fall eines Sturzes während des Kontrollpunkts).

Dies wirkt sich wie folgt auf den Datenfluss aus: Wir erhalten einen negativen Effekt (CPU) aufgrund der Notwendigkeit, die Seite jedes Mal vor dem Schreiben auf die Festplatte zu komprimieren, und einen positiven Effekt (Festplatten-E / A), indem wir die Menge der aufgezeichneten Daten reduzieren. Dementsprechend ist hier alles einfach. Wenn die Systemleistung auf der CPU liegt, kommt es zu einer leichten Verschlechterung. Wenn bei der Festplatten-E / A eine Erhöhung auftritt.

Indirekt wirkt sich die Reduzierung der Größe von WALs auch auf (positiv) Streams aus, die WAL-Segmente in das Archiv und die WAL-Komprimierungsströme ablegen.

Reale Leistungstests in unserer Umgebung mit synthetischen Daten zeigten einen geringen Anstieg (Durchsatz um 10 bis 15% erhöht, Latenz um 10 bis 15% verringert).

So aktivieren und konfigurieren Sie


Die Mindestversion von Apache Ignite ist 2.8. Das Einschalten und Einstellen erfolgt wie folgt:

  • Der Klassenpfad muss über ein Zündkomprimierungsmodul verfügen. Standardmäßig befindet es sich in der Apache Ignite-Distribution im Verzeichnis libs / optional und ist nicht im Klassenpfad enthalten. Sie können das Verzeichnis einfach um eine Ebene nach oben in die Bibliothek verschieben. Wenn es dann über ignite.sh gestartet wird, wird es automatisch aktiviert.
  • Die Persistenz muss aktiviert sein (Aktiviert durch DataRegionConfiguration.setPersistenceEnabled(true)).
  • DataStorageConfiguration.setWalPageCompression(), ( DISABLED).
  • DataStorageConfiguration.setWalPageCompression(), javadoc .


Die in Apache Ignite beschriebenen Datenkomprimierungsmechanismen können unabhängig voneinander verwendet werden, aber jede Kombination davon ist auch gültig. Das Verständnis der Prinzipien ihrer Arbeit bestimmt, wie sie zu Ihren Aufgaben in Ihrer Umgebung passen und was Sie opfern müssen, wenn Sie sie verwenden. Die Komprimierung von Festplattenseiten dient zum Komprimieren des Hauptspeichers und kann eine mittlere Komprimierung ermöglichen. Die WAL-Seiten-Snapshot-Komprimierung führt zu einem durchschnittlichen Komprimierungsgrad bereits vorhandener WAL-Dateien, während die Leistung wahrscheinlich sogar verbessert wird. Die WAL-Komprimierung wirkt sich nicht positiv auf die Leistung aus, minimiert jedoch die Größe der WAL-Dateien durch Löschen physischer Datensätze.

All Articles