Datenkomprimierung in Apache Ignite. Sbers Erfahrung

Datenkomprimierung in Apache Ignite. Sbers ErfahrungBei der Arbeit mit großen Datenmengen kann es manchmal zu dem Problem kommen, dass nicht genügend Speicherplatz zur Verfügung steht. Eine Möglichkeit, dieses Problem zu lösen, ist die Komprimierung, dank derer Sie es sich leisten können, auf derselben Ausrüstung das Speichervolumen zu erhöhen. In diesem Artikel schauen wir uns an, wie die Datenkomprimierung in Apache Ignite funktioniert. In diesem Artikel werden nur die im Produkt implementierten Festplattenkomprimierungsmethoden beschrieben. Andere Methoden der Datenkomprimierung (über das Netzwerk, im Speicher), unabhängig davon, ob sie implementiert sind oder nicht, bleiben außerhalb des Anwendungsbereichs.

Wenn also der Persistenzmodus aktiviert ist, beginnt Ignite aufgrund von Datenänderungen in den Caches mit dem Schreiben auf die Festplatte:

  1. Inhalt von Caches
  2. Write Ahead Log (im Folgenden einfach WAL)

Es gibt seit geraumer Zeit einen Mechanismus zur WAL-Komprimierung, die sogenannte WAL-Komprimierung. Mit dem kürzlich veröffentlichten Apache Ignite 2.8 wurden zwei weitere Mechanismen eingeführt, mit denen Sie Daten auf der Festplatte komprimieren können: Festplattenseitenkomprimierung zum Komprimieren des Inhalts von Caches und WAL-Seiten-Snapshot-Komprimierung zum Komprimieren einiger WAL-Einträge. Weitere Einzelheiten zu allen drei dieser Mechanismen finden Sie weiter unten.

Komprimierung von Festplattenseiten

Wie funktioniert das

Werfen wir zunächst einen kurzen Blick darauf, wie Ignite Daten speichert. Zur Speicherung wird der Seitenspeicher verwendet. Die Seitengröße wird am Anfang des Knotens festgelegt und kann zu einem späteren Zeitpunkt nicht geändert werden; außerdem muss die Seitengröße eine Zweierpotenz und ein Vielfaches der Blockgröße des Dateisystems sein. Seiten werden nach Bedarf von der Festplatte in den RAM geladen; die Größe der Daten auf der Festplatte kann die Menge des zugewiesenen RAM überschreiten. Wenn im RAM nicht genügend Speicherplatz vorhanden ist, um eine Seite von der Festplatte zu laden, werden alte, nicht mehr verwendete Seiten aus dem RAM entfernt.

Die Daten werden in folgender Form auf der Festplatte gespeichert: Für jede Partition jeder Cache-Gruppe wird eine separate Datei erstellt; in dieser Datei erscheinen die Seiten nacheinander in aufsteigender Indexreihenfolge. Der vollständige Seitenbezeichner enthält den Cachegruppenbezeichner, die Partitionsnummer und den Seitenindex in der Datei. Mithilfe der vollständigen Seitenkennung können wir somit die Datei und den Offset in der Datei für jede Seite eindeutig bestimmen. Weitere Informationen zum Auslagern von Speicher finden Sie im Apache Ignite-Wiki-Artikel: Ignite Persistent Store – unter der Haube.

Der Mechanismus zur Komprimierung von Festplattenseiten funktioniert, wie der Name schon vermuten lässt, auf Seitenebene. Wenn dieser Mechanismus aktiviert ist, werden die Daten im RAM unverändert und ohne Komprimierung verarbeitet. Wenn Seiten jedoch vom RAM auf die Festplatte gespeichert werden, werden sie komprimiert.

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

  • Anhand des Seitenindex können wir den Versatz, um den es sich in der Datei befindet, nicht berechnen.
  • Es ist nicht klar, was mit Seiten geschehen soll, die nicht am Ende der Datei stehen und deren Größe ändern. Wenn die Seitengröße kleiner wird, verschwindet der dadurch frei gewordene Platz. Wenn die Seitengröße zunimmt, müssen Sie nach einer neuen Stelle in der Datei dafür suchen.
  • Wenn sich eine Seite um eine Anzahl von Bytes verschiebt, die kein Vielfaches der Dateisystemblockgröße ist, muss beim Lesen oder Schreiben ein weiterer Dateisystemblock berührt werden, was zu Leistungseinbußen führen kann.

Um zu vermeiden, dass diese Probleme auf eigener Ebene gelöst werden, verwendet die Festplattenseitenkomprimierung in Apache Ignite einen Dateisystemmechanismus namens „Sparse Files“. Eine Datei mit geringer Dichte ist eine Datei, in der einige mit Nullen gefüllte Bereiche als „Löcher“ markiert werden können. In diesem Fall werden keine Dateisystemblöcke zum Speichern dieser Lücken zugewiesen, was zu einer Einsparung von Speicherplatz auf der Festplatte führt.

Es ist logisch, dass zum Freigeben eines Dateisystemblocks die Größe des Lochs größer oder gleich dem Dateisystemblock sein muss, was eine zusätzliche Einschränkung der Seitengröße und von Apache Ignite mit sich bringt: Damit die Komprimierung irgendeine Wirkung hat, Die Seitengröße muss unbedingt größer sein als die Größe des Dateisystemblocks. Wenn die Seitengröße gleich der Blockgröße ist, können wir niemals einen einzelnen Block freigeben, da die komprimierte Seite 0 Bytes belegen muss, um einen einzelnen Block freizugeben. 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 abschließende Beschreibung der Funktionsweise 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 Dateisystemblöcke zulässt, wird die Seite in komprimierter Form geschrieben und anstelle der freigegebenen Blöcke wird ein „Loch“ erstellt (ein Systemaufruf wird ausgeführt). fallocate() mit der Punch-Hole-Flagge). Wenn die Größe der komprimierten Seite die Freigabe der Blöcke nicht zulässt, wird die Seite unverändert und unkomprimiert gespeichert. Alle Seitenversätze werden auf die gleiche Weise wie ohne Komprimierung berechnet, indem der Seitenindex mit der Seitengröße multipliziert wird. Es ist kein eigener Seitenumzug erforderlich. Seitenoffsets liegen, genau wie ohne Komprimierung, an den Grenzen von Dateisystemblöcken.

Datenkomprimierung in Apache Ignite. Sbers Erfahrung

In der aktuellen Implementierung kann Ignite nur mit Dateien mit geringer Dichte unter dem Linux-Betriebssystem arbeiten; dementsprechend kann die Komprimierung von Festplattenseiten nur aktiviert werden, wenn Ignite auf 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), bei dem nur ungenutzter Platz auf der Seite verworfen wird, ohne die verbleibenden Daten zu komprimieren, was die Belastung der CPU im Vergleich zu den zuvor aufgeführten Algorithmen verringert.

Auswirkungen auf die Leistung

Leider habe ich keine tatsächlichen Leistungsmessungen an realen Ständen durchgeführt, da wir nicht planen, diesen Mechanismus in der Produktion einzusetzen, aber wir können theoretisch spekulieren, wo wir verlieren und wo wir gewinnen werden.

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

  • Beim Durchführen eines Lesevorgangs wird zunächst im RAM gesucht. Wenn die Suche nicht erfolgreich ist, wird die Seite von demselben Thread, der den Lesevorgang ausführt, von der Festplatte in den RAM geladen.
  • Wenn ein Schreibvorgang ausgeführt wird, wird die Seite im RAM als fehlerhaft markiert, aber die Seite wird vom Thread, der den Schreibvorgang ausführt, nicht sofort physisch auf der Festplatte gespeichert. Alle fehlerhaften Seiten werden später im Prüfpunktprozess in separaten Threads auf der Festplatte gespeichert.

Die Auswirkungen auf Lesevorgänge sind also:

  • Positiv (Festplatten-E/A), aufgrund einer Verringerung der Anzahl der gelesenen Dateisystemblöcke.
  • Negativ (CPU), aufgrund der zusätzlichen Belastung, die das Betriebssystem für die Arbeit mit Dateien mit geringer Dichte benötigt. Es ist auch möglich, dass hier implizit zusätzliche IO-Operationen erscheinen, um eine komplexere Dateistruktur mit geringer Dichte zu speichern (leider kenne ich nicht alle Details zur Funktionsweise von Dateien mit geringer Dichte).
  • Negativ (CPU), da Seiten dekomprimiert werden müssen.
  • Es gibt keine Auswirkungen auf Schreibvorgänge.
  • Auswirkungen auf den Checkpoint-Prozess (hier ist alles ähnlich wie bei Lesevorgängen):
  • Positiv (Festplatten-E/A), da die Anzahl der geschriebenen Dateisystemblöcke abnimmt.
  • Negativ (CPU, möglicherweise Festplatten-IO), da mit Dateien mit geringer Dichte gearbeitet wird.
  • Negativ (CPU), da eine Seitenkomprimierung erforderlich ist.

Welche Seite der Waage wird den Ausschlag geben? Das hängt alles stark von der Umgebung ab, aber ich neige dazu zu glauben, dass die Komprimierung von Festplattenseiten auf den meisten Systemen höchstwahrscheinlich zu Leistungseinbußen führt. 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, ist die Mindestversion von Apache Ignite, die die Festplattenseitenkomprimierung unterstützt, 2.8 und nur das Linux-Betriebssystem wird unterstützt. Aktivieren und konfigurieren Sie wie folgt:

  • Im Klassenpfad muss ein Ignite-Compression-Modul vorhanden sein. 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 eine Ebene nach oben in libs verschieben und es dann automatisch aktivieren, wenn Sie es über ignite.sh ausführen.
  • Persistenz muss aktiviert sein (Aktiviert über DataRegionConfiguration.setPersistenceEnabled(true)).
  • Die Seitengröße muss größer sein als die Blockgröße des Dateisystems (Sie können sie mit festlegen). DataStorageConfiguration.setPageSize() ).
  • Für jeden Cache, dessen Daten komprimiert werden müssen, müssen Sie die Komprimierungsmethode und (optional) die Komprimierungsstufe (Methoden) konfigurieren CacheConfiguration.setDiskPageCompression() , CacheConfiguration.setDiskPageCompressionLevel()).

WAL-Verdichtung

Wie funktioniert das

Was ist WAL und warum wird es benötigt? Ganz kurz gesagt: Hierbei handelt es sich um ein Protokoll, das alle Ereignisse enthält, die letztendlich die Seitenspeicherung verändern. Es wird in erster Linie benötigt, um sich im Falle eines Sturzes erholen zu können. Bevor die Kontrolle an den Benutzer übergeben wird, muss jeder Vorgang zunächst ein Ereignis in WAL aufzeichnen, damit er im Fehlerfall im Protokoll wiedergegeben und alle Vorgänge wiederhergestellt werden können, für die der Benutzer eine erfolgreiche Antwort erhalten hat, auch wenn diese Vorgänge nicht erfolgreich waren hatte keine Zeit, sich im Seitenspeicher auf der Festplatte widerzuspiegeln (oben wurde bereits beschrieben, dass das eigentliche Schreiben in den Seitenspeicher in einem Prozess namens „Checkpointing“ mit einer gewissen Verzögerung durch separate Threads erfolgt).

Einträge in WAL sind in logische und physische unterteilt. Boolesche Werte sind die Schlüssel und Werte selbst. Physisch – spiegelt Änderungen an Seiten im Seitenspeicher wider. Während 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 Absturzes benötigt, und Datensätze werden nur seit dem letzten erfolgreichen Prüfpunkt benötigt. Wir gehen hier nicht ins Detail und erklären, warum das so funktioniert, Interessenten können aber auf den bereits erwähnten Artikel im Apache Ignite Wiki verweisen: Ignite Persistent Store – unter der Haube.

Oft gibt es pro logischem Datensatz mehrere physische Datensätze. Das heißt, dass sich beispielsweise ein Put-Vorgang in den Cache auf mehrere Seiten im Seitenspeicher auswirkt (eine Seite mit den Daten selbst, Seiten mit Indizes, Seiten mit freien Listen). Bei einigen synthetischen Tests stellte ich fest, dass physische Datensätze bis zu 90 % der WAL-Datei einnahmen. Sie werden jedoch nur für sehr kurze Zeit benötigt (standardmäßig beträgt das Intervall zwischen den Kontrollpunkten 3 Minuten). Es wäre logisch, diese Daten loszuwerden, nachdem sie ihre Relevanz verloren haben. Genau das macht der WAL-Komprimierungsmechanismus: Er entfernt physische Datensätze und komprimiert die verbleibenden logischen Datensätze mithilfe von zip, während die Dateigröße sehr erheblich reduziert wird (manchmal um das Zehnfache).

Physisch besteht WAL aus mehreren Segmenten (standardmäßig 10) mit fester Größe (standardmäßig 64 MB), die zirkulär überschrieben werden. Sobald das aktuelle Segment gefüllt ist, wird das nächste Segment als aktuell zugewiesen und das gefüllte Segment wird von einem separaten Thread in das Archiv kopiert. Die WAL-Komprimierung funktioniert bereits mit Archivsegmenten. Als separater Thread überwacht es außerdem die Ausführung des Prüfpunkts und beginnt mit der Komprimierung in Archivsegmenten, für die keine physischen Datensätze mehr benötigt werden.

Datenkomprimierung in Apache Ignite. Sbers Erfahrung

Auswirkungen auf die Leistung

Da die WAL-Komprimierung als separater Thread ausgeführt wird, sollte es keine direkten Auswirkungen auf die ausgeführten Vorgänge geben. Allerdings wird dadurch die CPU (Komprimierung) und die Festplatte (Lesen jedes WAL-Segments aus dem Archiv und Schreiben der komprimierten Segmente) zusätzlich im Hintergrund belastet. Wenn das System also mit maximaler Kapazität läuft, kommt es auch zu Leistungseinbußen.

So aktivieren und konfigurieren Sie

Sie können die WAL-Komprimierung mithilfe der Eigenschaft aktivieren WalCompactionEnabled в DataStorageConfiguration (DataStorageConfiguration.setWalCompactionEnabled(true)). Außerdem können Sie mit der Methode DataStorageConfiguration.setWalCompactionLevel() die Komprimierungsstufe festlegen, wenn Sie mit dem Standardwert (BEST_SPEED) nicht zufrieden sind.

WAL-Seiten-Snapshot-Komprimierung

Wie funktioniert das

Wir haben bereits herausgefunden, dass in WAL Datensätze in logische und physische unterteilt werden. Für jede Änderung an jeder Seite wird ein physischer WAL-Datensatz im Seitenspeicher generiert. Physische Datensätze wiederum werden ebenfalls in zwei Untertypen unterteilt: Seiten-Snapshot-Datensatz und Delta-Datensatz. Jedes Mal, wenn wir etwas auf einer Seite ändern und sie von einem sauberen Zustand in einen schmutzigen Zustand überführen, wird eine vollständige Kopie dieser Seite in WAL (Seiten-Snapshot-Datensatz) gespeichert. Selbst wenn wir nur ein Byte in WAL geändert haben, ist der Datensatz etwas größer als die Seitengröße. Wenn wir auf einer bereits schmutzigen Seite etwas ändern, wird in WAL ein Delta-Datensatz gebildet, der nur Änderungen gegenüber dem vorherigen Zustand der Seite widerspiegelt, nicht jedoch die gesamte Seite. Da das Zurücksetzen des Status von Seiten von „Dirty“ auf „Clean“ während des Checkpoint-Prozesses durchgeführt wird, bestehen unmittelbar nach dem Start des Checkpoints fast alle physischen Datensätze nur aus Snapshots von Seiten (da alle Seiten unmittelbar nach dem Start des Checkpoints sauber sind). Wenn wir uns dann dem nächsten Prüfpunkt nähern, beginnt der Delta-Datensatzanteil zu wachsen und wird zu Beginn des nächsten Prüfpunkts wieder zurückgesetzt. Messungen in einigen synthetischen Tests zeigten, dass der Anteil der Seiten-Snapshots am Gesamtvolumen der physischen Aufzeichnungen 2 % erreicht.

Die Idee der WAL-Seiten-Snapshot-Komprimierung besteht darin, Seiten-Snapshots mit einem vorgefertigten Seitenkomprimierungstool zu komprimieren (siehe Komprimierung von Festplattenseiten). Gleichzeitig werden in WAL Datensätze nacheinander im Nur-Anhänge-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 keine Dateien mit geringer Dichte alle; dementsprechend funktioniert dieser Mechanismus nicht nur auf dem Betriebssystem Linux. Außerdem spielt es für uns keine Rolle mehr, 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 Festplattenseitenkomprimierung, bei der wir die komprimierte Seite nur speichern, wenn wir mehr als 1 Dateisystemblock freigegeben haben.

Seiten sind stark komprimierbare Daten, ihr Anteil am gesamten WAL-Volumen ist sehr hoch, sodass wir ohne Änderung des WAL-Dateiformats eine deutliche Reduzierung der Größe erreichen können. Eine Komprimierung, einschließlich logischer Datensätze, würde beispielsweise für externe Verbraucher, die möglicherweise an logischen Datensätzen interessiert sind, eine Änderung des Formats und einen Kompatibilitätsverlust erfordern, würde jedoch nicht zu einer wesentlichen Reduzierung der Dateigröße führen.

Wie bei der Komprimierung von Festplattenseiten kann auch bei der WAL-Seiten-Snapshot-Komprimierung ZSTD, LZ4, Snappy-Komprimierungsalgorithmen sowie der SKIP_GARBAGE-Modus verwendet werden.

Auswirkungen auf die Leistung

Es ist nicht schwer zu erkennen, dass die direkte Aktivierung der WAL-Seiten-Snapshot-Komprimierung nur Threads betrifft, die Daten in den Seitenspeicher schreiben, also die Threads, die Daten in Caches ändern. Das Lesen physischer Datensätze aus WAL erfolgt nur einmal, und zwar in dem Moment, in dem der Knoten nach einem Absturz angehoben wird (und nur, wenn er während eines Prüfpunkts abstürzt).

Dies wirkt sich auf Threads aus, die Daten auf folgende Weise ändern: Wir erhalten einen negativen Effekt (CPU), da die Seite jedes Mal komprimiert werden muss, bevor auf die Festplatte geschrieben wird, und einen positiven Effekt (Festplatten-E/A), da die Menge reduziert wird Daten geschrieben. Dementsprechend ist hier alles einfach: Wenn die Systemleistung durch die CPU begrenzt wird, erhalten wir eine leichte Verschlechterung, wenn sie durch Festplatten-I/O begrenzt wird, erhalten wir eine Steigerung.

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

Echte Leistungstests in unserer Umgebung mit synthetischen Daten zeigten einen leichten Anstieg (Durchsatz stieg um 10–15 %, Latenz verringerte sich um 10–15 %).

So aktivieren und konfigurieren Sie

Mindestversion von Apache Ignite: 2.8. Aktivieren und konfigurieren Sie wie folgt:

  • Im Klassenpfad muss ein Ignite-Compression-Modul vorhanden sein. 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 eine Ebene nach oben in libs verschieben und es dann automatisch aktivieren, wenn Sie es über ignite.sh ausführen.
  • Persistenz muss aktiviert sein (Aktiviert über DataRegionConfiguration.setPersistenceEnabled(true)).
  • Der Komprimierungsmodus muss über die Methode eingestellt werden DataStorageConfiguration.setWalPageCompression(), ist die Komprimierung standardmäßig deaktiviert (DISABLED-Modus).
  • Optional können Sie die Komprimierungsstufe mithilfe der Methode festlegen DataStorageConfiguration.setWalPageCompression(), siehe Javadoc für die Methode für gültige Werte für jeden Modus.

Abschluss

Die betrachteten Datenkomprimierungsmechanismen in Apache Ignite können unabhängig voneinander verwendet werden, aber auch jede Kombination davon ist akzeptabel. Wenn Sie verstehen, wie sie funktionieren, können Sie feststellen, wie geeignet sie für Ihre Aufgaben in Ihrer Umgebung sind und was Sie bei der Verwendung opfern müssen. Die Festplattenseitenkomprimierung dient der Komprimierung des Hauptspeichers und kann ein mittleres Komprimierungsverhältnis ergeben. Die WAL-Seiten-Snapshot-Komprimierung ergibt einen durchschnittlichen Komprimierungsgrad für WAL-Dateien und wird höchstwahrscheinlich sogar die Leistung verbessern. Die WAL-Komprimierung wirkt sich nicht positiv auf die Leistung aus, reduziert jedoch die Größe der WAL-Dateien so weit wie möglich, indem physische Datensätze entfernt werden.

Source: habr.com

Kommentar hinzufügen