Beim Arbeiten mit großen Datenmengen kann das Problem des Platzmangels auf den Festplatten akut werden. Eine der Lösungen für dieses Problem ist die Kompression, durch die es möglich wird, den Speicherplatz auf der gleichen Hardware zu erweitern. In diesem Artikel werden wir untersuchen, wie die Datenkompression in Apache Ignite funktioniert. Es werden nur die im Produkt implementierten Methoden zur Kompression auf der Festplatte beschrieben. Andere Methoden zur Datenkompression (über das Netzwerk, im Speicher), ob implementiert oder nicht, bleiben unberücksichtigt.
Wenn der Persistenzmodus aktiviert ist, beginnt Ignite infolge von Änderungen der Daten in den Caches, diese auf die Festplatte zu schreiben:
- Inhalte der Caches
- Proaktive Aufzeichnung (Write Ahead Log, kurz WAL)
Für die Kompression des WAL existiert bereits seit geraumer Zeit ein Mechanismus, der WAL-Compaction genannt wird. In der neu veröffentlichten Version Apache Ignite 2.8 wurden zwei weitere Mechanismen eingeführt, um Daten auf der Festplatte zu komprimieren: die Disk Page Compression zur Komprimierung der Inhalte der Caches und die WAL Page Snapshot Compression zur Komprimierung bestimmter WAL-Einträge. Im Folgenden finden Sie detaillierte Informationen zu diesen drei Mechanismen.
Disk Page Compression
Wie es funktioniert
Lassen Sie uns zunächst kurz darauf eingehen, wie Ignite Daten speichert. Für die Speicherung wird seitenbasiertes Gedächtnis verwendet. Die Größe der Seiten wird beim Start des Knotens festgelegt und kann in späteren Phasen nicht mehr verändert werden; sie muss zudem eine Potenz von zwei sein und ein Vielfaches der Blockgröße des Dateisystems darstellen. Seiten werden bei Bedarf aus dem Speicher geladen, wobei die Daten auf der Festplatte den zugewiesenen RAM überschreiten können. Wenn der RAM nicht ausreicht, um eine Seite von der Festplatte zu laden, werden alte, nicht mehr benötigte Seiten aus dem RAM verdrängt.
Auf der Festplatte werden die Daten wie folgt gespeichert: Für jede Partition jeder Cache-Gruppe wird eine separate Datei erstellt, in der die Seiten der Reihe nach in aufsteigender Indexreihenfolge gespeichert werden. Der vollständige Seitenidentifikator enthält die Kennung der Cache-Gruppe, die Nummer der Partition und den Index der Seite in der Datei. Somit können wir anhand des vollständigen Seitenidentifikators die Datei und den Offset in der Datei für jede Seite eindeutig bestimmen. Detaillierte Informationen zur Struktur des seitenbasierten Gedächtnisses finden Sie im Artikel auf der Apache Ignite Wiki: .
Der Mechanismus der Seitenkompression für Festplatten funktioniert, wie der Name schon sagt, auf Seitenebene. Bei Aktivierung dieses Mechanismus erfolgt der Umgang mit Daten im RAM ohne Kompression, aber beim Speichern der Seiten von RAM auf die Festplatte wird eine Kompression durchgeführt.
Das separate Komprimieren jeder Seite allein löst das Problem jedoch nicht. Es ist notwendig, die Größe der endgültigen Datendateien zu reduzieren. Wenn die Größe einer Seite nicht mehr festgelegt ist, können wir die Seiten nicht der Reihe nach in die Datei schreiben, da dies eine Reihe von Problemen verursachen kann:
- Wir können nicht mit Hilfe des Seitenindex den Offset berechnen, an dem sie in der Datei gespeichert ist.
- Es ist unklar, was mit Seiten zu tun ist, die sich nicht am Ende der Datei befinden und deren Größe sich ändert. Wenn die Größe einer Seite verringert wird, geht der Platz, den sie freigibt, verloren. Wenn die Größe einer Seite zunimmt, muss ein neuer Platz in der Datei für sie gefunden werden.
- Wenn eine Seite um eine Anzahl von Bytes verschoben wird, die nicht ein Vielfaches der Blockgröße des Dateisystems ist, muss beim Lesen oder Schreiben ein zusätzlicher Dateisystemblock angesprochen werden, was zu einer Verschlechterung der Leistung führen kann.
Um solche Probleme nicht selbst auf Ihrer Ebene lösen zu müssen, verwendet die Disk Page Compression in Apache Ignite einen Mechanismus des Dateisystems, der als Sparse-Dateien bezeichnet wird. Eine Sparse-Datei ist eine Datei, bei der bestimmte, mit Nullen gefüllte Bereiche als 'Löcher' markiert werden können. Dadurch werden keine Dateisystemblöcke zur Speicherung dieser Löcher reserviert, was zu einer Einsparung von Speicherplatz auf der Festplatte führt.
Es ist logisch, dass der Freigabebereich der Dateisystemblöcke mindestens so groß sein muss wie der Dateisystemblock selbst, was eine zusätzliche Einschränkung für die Seitengröße in Apache Ignite darstellt: Damit die Kompression irgendeinen Effekt hat, muss die Seitengröße unbedingt größer als die Blockgröße sein. Wenn die Seitengröße der Blockgröße entspricht, können wir keinen einzigen Block freigeben, da die komprimierte Seite 0 Byte einnehmen müsste, um genau einen Block freizugeben. Wenn die Seitengröße jedoch der Größe von 2 oder 4 Blöcken entspricht, können wir mindestens einen Block freigeben, wenn unsere Seite mindestens auf 50 % oder 75 % komprimiert wird.
Somit ergibt sich folgende Zusammenfassung der Arbeitsweise des Mechanismus: Beim Schreiben einer Seite auf die Festplatte wird versucht, die Seite zu komprimieren. Wenn die Größe der komprimierten Seite es ermöglicht, einen oder mehrere Dateisystemblöcke freizugeben, wird die Seite in komprimierter Form gespeichert, und an der Stelle der freigegebenen Blöcke wird ein «Loch» erstellt (ein Systemaufruf wird ausgeführt). fallocate() mit dem „Punch Hole“-Symbol). Wenn die Größe der komprimierten Seite nicht ausreicht, um Blöcke freizugeben, bleibt die Seite unverändert im unkomprimierten Zustand. Alle Seitenoffsets werden wie bei der Kompression auch berechnet, indem der Seitenindex mit der Seitengröße multipliziert wird. Eine manuelle Relokation der Seiten ist nicht erforderlich. Seitenoffsets fallen, genau wie ohne Kompression, an die Grenzen der Blocks im Dateisystem.

In der aktuellen Implementierung kann Ignite nur mit Sparse-Dateien unter Linux arbeiten, daher kann die Seitenkompression nur aktiviert werden, wenn Ignite auf diesem Betriebssystem verwendet wird.
Die für die Seitenkompression verwendbaren Algorithmen sind: ZSTD, LZ4, Snappy. Darüber hinaus gibt es einen Betriebsmodus (SKIP_GARBAGE), bei dem nur der ungenutzte Platz in der Seite ohne Anwendung von Kompression auf den verbleibenden Daten entfernt wird, was die CPU-Belastung im Vergleich zu den zuvor genannten Algorithmen reduziert.
Einfluss auf die Leistungsfähigkeit
Leider habe ich keine tatsächlichen Leistungsbenchmarks auf realen Ständen durchgeführt, da wir nicht planen, diesen Mechanismus in der Produktion zu verwenden. Theoretisch können wir jedoch über die möglichen Verluste und Gewinne nachdenken.
Dafür müssen wir uns erinnern, wie das Lesen und Schreiben von Seiten beim Zugriff auf diese erfolgt:
- Bei einer Leseoperation wird zunächst in den RAM gesucht. Wenn die Suche erfolglos ist, wird die Seite im selben Thread von der Festplatte in den RAM geladen, der die Leseoperation durchführt.
- Bei einer Schreiboperation wird die Seite im RAM als schmutzig markiert, wobei die physische Speicherung der Seite auf der Festplatte nicht sofort im Thread erfolgt, der die Schreiboperation durchführt. Alle schmutzigen Seiten werden später während des Checkpoints von separaten Threads auf die Festplatte gespeichert.
Somit hat es Einfluss auf Leseoperationen:
- Positiv (disk IO), aufgrund der Verringerung der Anzahl der gelesenen Blöcke im Dateisystem.
- Negativ (CPU), aufgrund der zusätzlichen Belastung, die das Betriebssystem benötigt, um mit Sparse-Dateien zu arbeiten. Möglicherweise treten hier auch implizit zusätzliche IO-Operationen auf, um die komplexere Struktur der Sparse-Datei zu speichern (leider bin ich mit allen Details der Funktionsweise von Sparse-Dateien nicht vertraut).
- Negativ (CPU), wegen der Notwendigkeit, Seiten zu dekomprimieren.
- Es gibt keine Auswirkungen auf Schreiboperationen.
- Einfluss auf den Checkpoint-Prozess (hier gilt das gleiche wie bei Leseoperationen):
- Positiv (Disk IO), da die Anzahl der geschriebenen Blöcke des Dateisystems verringert wird.
- Negativ (CPU, möglicherweise Disk IO), aufgrund der Arbeit mit Sparse-Dateien.
- Negativ (CPU), wegen der Notwendigkeit, Seiten zu komprimieren.
Welche Seite wird überwiegen? Das hängt stark von der Umgebung ab, aber ich tendiere dazu, dass die Disk-Seitenkompression eher zu einer Leistungsminderung in den meisten Systemen führt. Zumal Tests mit anderen DBMS, die einen ähnlichen Ansatz mit Sparse-Dateien verwenden, einen Leistungsabfall bei aktivierter Komprimierung zeigen.
Wie man es aktiviert und konfiguriert
Wie bereits erwähnt, ist die minimale Version von Apache Ignite, die die Kompression von Festplattenseiten unterstützt, 2.8 und wird ausschließlich von dem Betriebssystem Linux unterstützt. Die Aktivierung und Konfiguration erfolgen wie folgt:
- Im class-path muss das Modul ignite-compression vorhanden sein. Standardmäßig befindet es sich im Apache Ignite-Distribution im Verzeichnis libs/optional und ist nicht im class-path enthalten. Sie können es einfach ein Level höher in libs verschieben, und dann wird es beim Start über ignite.sh automatisch eingebunden.
- Die Persistenz muss aktiviert sein (Aktivierung erfolgt über
DataRegionConfiguration.setPersistenceEnabled(true)). - Die Seitengröße muss größer sein als die Blockgröße des Dateisystems (verstellbar über
DataStorageConfiguration.setPageSize()). - Für jeden Cache, dessen Daten komprimiert werden sollen, müssen in der Konfiguration die Komprimierungsmethode und (optional) das Komprimierungsniveau eingestellt werden (Methoden
CacheConfiguration.setDiskPageCompression(), CacheConfiguration.setDiskPageCompressionLevel()).
WAL-Komprimierung
Wie es funktioniert
Was ist WAL und warum ist es wichtig? Kurz gesagt: Es handelt sich um ein Protokoll, in dem alle Ereignisse erfasst werden, die letztendlich das Seitenlager beeinflussen. Es ist in erster Linie für die Wiederherstellung im Falle eines Ausfalls erforderlich. Jede Operation muss, bevor sie die Kontrolle an den Benutzer zurückgibt, ein Ereignis im WAL protokollieren, damit im Falle eines Ausfalls die Protokolle wieder abgespielt werden können und alle Operationen, für die der Benutzer eine erfolgreiche Antwort erhalten hat, wiederhergestellt werden, selbst wenn diese Operationen noch nicht im Seitenlager auf der Festplatte festgehalten wurden (wie bereits erwähnt, erfolgt die tatsächliche Speicherung im Seitenlager im Rahmen eines Prozesses, der als „Checkpoint“ bezeichnet wird, mit einer gewissen Verzögerung durch separate Threads).
WAL-Einträge werden in logische und physische Aufzeichnungen unterteilt. Logische Aufzeichnungen sind die Schlüssel und Werte selbst. Physische Aufzeichnungen spiegeln die Änderungen der Seiten im Seiten-Speicher wider. Während logische Aufzeichnungen in anderen Situationen nützlich sein können, sind physische Aufzeichnungen nur für die Wiederherstellung im Falle eines Ausfalls erforderlich und beinhalten nur Aufzeichnungen seit dem letzten erfolgreichen Checkpoint. Hier werden wir nicht ins Detail gehen und erklären, warum das so funktioniert, aber Interessierte können sich an den bereits erwähnten Artikel im Apache Ignite Wiki wenden: .
Auf einen logischen Eintrag entfallen häufig mehrere physische Einträge. Das heißt, zum Beispiel betrifft eine put-Operation im Cache mehrere Seiten im Seitenspeicher (die Seite mit den Daten, die Seiten mit den Indizes, die Seiten mit den Freiliste). In einigen synthetischen Tests stellte ich fest, dass physische Einträge bis zu 90 % des Volumens der WAL-Datei einnahmen. Dabei waren sie nur von sehr kurzer Dauer erforderlich (standardmäßig ist das Intervall zwischen den Checkpoints 3 Minuten). Es wäre logisch, diese Daten nach Verlust ihrer Relevanz zu entfernen. Genau das wird durch den WAL-Compaction-Mechanismus erreicht, der physische Einträge entfernt und die verbleibenden logischen Einträge komprimiert, wobei der Dateigröße erheblich reduziert wird (manchmal um das Zehnfache).
Die physische WAL besteht aus mehreren Segmenten (standardmäßig 10) von fester Größe (standardmäßig 64 MB), die im Rundlauf überschrieben werden. Sobald das aktuelle Segment voll ist, wird das nächste Segment zugewiesen, und das ausgefüllte Segment wird in einem separaten Stream archiviert. Die WAL-Komprimierung funktioniert bereits mit archivierten Segmenten. Außerdem überwacht sie in einem separaten Stream die Ausführung von Checkpoints und beginnt die Komprimierung für archivierte Segmente, deren physische Aufzeichnungen nicht mehr benötigt werden.

Einfluss auf die Leistungsfähigkeit
Da die WAL-Komprimierung in einem separaten Thread funktioniert, sollte sie keine direkten Auswirkungen auf die ausgeführten Operationen haben. Dennoch verursacht sie eine zusätzliche Hintergrundlast auf der CPU (Komprimierung) und der Festplatte (Lesen jedes WAL-Segments aus dem Archiv und Schreiben der komprimierten Segmente). Daher kann es bei einer Systemauslastung an der Leistungsgrenze ebenfalls zu einer Verschlechterung der Performance kommen.
Wie man es aktiviert und konfiguriert
Die WAL-Komprimierung kann durch die Eigenschaft WalCompactionEnabled in DataStorageConfiguration (DataStorageConfiguration.setWalCompactionEnabled(true)). Außerdem lässt sich der Komprimierungsgrad mit der Methode DataStorageConfiguration.setWalCompactionLevel() anpassen, falls der Standardwert (BEST_SPEED) nicht zufriedenstellend ist.
WAL-Seiten-Snapshot-Komprimierung
Wie es funktioniert
Es wurde bereits festgestellt, dass WAL-Aufzeichnungen in logische und physische unterteilt sind. Für jede Änderung jeder Seite im Seiten-Speicher wird ein physischer WAL-Eintrag erstellt. Physische Einträge werden wiederum in zwei Unterkategorien unterteilt: Page Snapshot Record und Delta Record. Jedes Mal, wenn wir etwas auf einer Seite ändern und sie von einem sauberen Zustand in einen schmutzigen versetzen, wird eine vollständige Kopie dieser Seite im WAL gespeichert (Page Snapshot Record – Snapshot der Seite). Selbst wenn wir nur ein einziges Byte ändern, wird im WAL ein Eintrag gespeichert, der etwas mehr als die Größe der Seite beträgt. Wenn wir jedoch etwas auf einer bereits schmutzigen Seite ändern, wird im WAL ein Delta Record erstellt, der nur die Änderungen im Vergleich zum vorherigen Zustand der Seite widerspiegelt, aber nicht die gesamte Seite. Da der Wechsel des Seitenzustands von schmutzig zu sauber im Rahmen eines Checkpoints erfolgt, bestehen zu Beginn des Checkpoints praktisch alle physischen Einträge nur aus Snapshots der Seiten (da alle Seiten direkt nach Beginn des Checkpoints sauber sind). Mit dem Fortschreiten hin zum nächsten Checkpoint beginnt der Anteil der Delta Records zu steigen und wird zu Beginn des nächsten Checkpoints erneut zurückgesetzt. Messungen in einigen synthetischen Tests haben gezeigt, dass der Anteil der Snapshots der Seiten im Gesamtvolumen der physischen Einträge bis zu 90 % erreichen kann.
Die Idee der WAL-Seiten-Snapshot-Kompression besteht darin, Snapshots von Seiten mit bereits bestehenden Werkzeugen zur Seitenkompression zu komprimieren (siehe Disk-Seitenkompression). Dabei werden die WAL-Einträge sequentiell im Append-Only-Modus gespeichert, und es ist keine Bindung der Einträge an die Grenzen der Dateisystemblöcke erforderlich. Daher benötigen wir, im Gegensatz zum Mechanismus der Disk-Seitenkompression, hier keine sparse Dateien, sodass dieser Mechanismus nicht nur unter Linux funktioniert. Außerdem ist es uns nicht mehr wichtig, wie stark wir die Seite komprimiert haben. Selbst wenn wir nur 1 Byte freigeben, ist das bereits ein positives Ergebnis, und wir können komprimierte Daten im WAL speichern, im Gegensatz zur Disk-Seitenkompression, bei der wir die komprimierte Seite nur speichern, wenn wir mehr als 1 Block des Dateisystems freigegeben haben.
Seiten sind gut komprimierbare Daten, wobei ihr Anteil am gesamten WAL-Volumen sehr hoch ist. Indem wir das Format der WAL-Datei nicht ändern, können wir eine erhebliche Reduzierung ihrer Größe erzielen. Eine Komprimierung der logischen Aufzeichnungen würde hingegen eine Änderung des Formats erfordern und zu einem Verlust der Kompatibilität führen, insbesondere für externe Benutzer, die möglicherweise an logischen Aufzeichnungen interessiert sind, ohne dabei eine signifikante Verringerung des Datei-Volumens zu erzielen.
Wie bei der Disk-Seitenkompression können auch für die WAL-Seiten-Snapshot-Kompression die Kompressionsalgorithmen ZSTD, LZ4, Snappy sowie der SKIP_GARBAGE-Modus verwendet werden.
Einfluss auf die Leistungsfähigkeit
Wie leicht zu erkennen ist, wirkt sich die direkte Aktivierung der WAL-Seiten-Snapshot-Kompression nur auf die Threads aus, die Daten in den Seitenspeicher schreiben, also auf die Threads, die Daten in den Caches ändern. Das Lesen von physischen WAL-Aufzeichnungen erfolgt nur einmalig, wenn der Knoten nach einem Absturz wieder hochgefahren wird (und nur im Fall eines Absturzes während des Checkpoints).
Die Auswirkungen auf datenändernde Streams sind wie folgt: Wir erfahren eine negative Auswirkung (CPU) aufgrund der Notwendigkeit, die Seite vor der Speicherung auf der Festplatte jedes Mal zu komprimieren, und eine positive Auswirkung (Disk IO) durch die Verringerung der zu schreibenden Daten. Daher ist es einfach: Wenn die Systemleistung an der CPU liegt, haben wir eine geringe Verschlechterung, wenn an der Festplattenein-/ausgabe, haben wir einen Anstieg.
Indirekt beeinflusst die Verringerung der WAL-Größe auch (positiv) die Streams, die WAL-Segmente in das Archiv verschieben, und die WAL-Kompaktierung.
Echte Leistungstests in unserer Umgebung mit synthetischen Daten zeigten einen leichten Anstieg (der Durchsatz stieg um 10%-15%, die Latenz verringerte sich um 10%-15%).
Wie man es aktiviert und konfiguriert
Minimale Version von Apache Ignite: 2.8. Die Aktivierung und Konfiguration erfolgt wie folgt:
- Im class-path muss das Modul ignite-compression vorhanden sein. Standardmäßig befindet es sich im Apache Ignite-Distribution im Verzeichnis libs/optional und ist nicht im class-path enthalten. Sie können es einfach ein Level höher in libs verschieben, und dann wird es beim Start über ignite.sh automatisch eingebunden.
- Die Persistenz muss aktiviert sein (Aktivierung erfolgt über
DataRegionConfiguration.setPersistenceEnabled(true)). - Der Komprimierungsmodus muss mit der Methode angegeben werden
DataStorageConfiguration.setWalPageCompression(), standardmäßig ist die Komprimierung deaktiviert (MODUS DISABLED). - Optional kann der Komprimierungsgrad mit der Methode angegeben werden
DataStorageConfiguration.setWalPageCompression(), zulässige Werte für jeden der Modi finden Sie in der Javadoc zu dieser Methode.
Fazit
Die in Apache Ignite behandelten Datenkomprimierungsmechanismen können unabhängig voneinander verwendet werden, aber auch beliebige Kombinationen sind zulässig. Das Verständnis der Funktionsprinzipien hilft dabei zu bestimmen, wie gut sie zu Ihren Aufgaben in Ihrer Umgebung passen und welche Kompromisse dabei eingegangen werden müssen. Die Disk-Seitenspeicherkompression ist für die Komprimierung des primären Speichers gedacht und kann eine mittlere Kompressionsrate erzielen. Die WAL-Seitenspeichersnapshot-Kompression erzielt ebenfalls eine mittlere Kompressionsrate für die WAL-Dateien, was wahrscheinlich sogar die Leistung steigert. Die WAL-Komprimierung hat jedoch keinen positiven Einfluss auf die Leistung, reduziert aber die Größe der WAL-Dateien erheblich, indem physische Aufzeichnungen entfernt werden.
Quelle: habr.com
