Gegevenscompressie in Apache Ignite. Ervaring van Sber

Gegevenscompressie in Apache Ignite. Ervaring van SberBij het werken met grote hoeveelheden gegevens kan zich soms het probleem van gebrek aan schijfruimte voordoen. Een manier om dit probleem op te lossen is compressie, waardoor u het zich kunt veroorloven om op dezelfde apparatuur de opslagvolumes te vergroten. In dit artikel zullen we bekijken hoe datacompressie werkt in Apache Ignite. In dit artikel worden alleen de schijfcompressiemethoden beschreven die in het product zijn geïmplementeerd. Andere methoden van datacompressie (via het netwerk, in het geheugen), al dan niet geïmplementeerd, blijven buiten het toepassingsgebied.

Dus als de persistentiemodus is ingeschakeld, begint Ignite, als gevolg van wijzigingen in gegevens in de caches, naar schijf te schrijven:

  1. Inhoud van caches
  2. Write Ahead Log (hierna kortweg WAL)

Er bestaat al geruime tijd een mechanisme voor WAL-compressie, genaamd WAL-compactie. De onlangs uitgebrachte Apache Ignite 2.8 introduceerde nog twee mechanismen waarmee je gegevens op schijf kunt comprimeren: schijfpaginacompressie voor het comprimeren van de inhoud van caches en WAL-pagina-snapshot-compressie voor het comprimeren van sommige WAL-items. Hieronder vindt u meer informatie over alle drie deze mechanismen.

Compressie van schijfpagina's

Hoe werkt dit

Laten we eerst eens heel kort kijken hoe Ignite gegevens opslaat. Paginageheugen wordt gebruikt voor opslag. De paginagrootte wordt ingesteld aan het begin van het knooppunt en kan in latere fasen niet worden gewijzigd; ook moet de paginagrootte een macht van twee zijn en een veelvoud van de blokgrootte van het bestandssysteem. Pagina's worden indien nodig vanaf schijf in het RAM geladen; de grootte van de gegevens op de schijf kan de hoeveelheid toegewezen RAM overschrijden. Als er niet genoeg ruimte in het RAM-geheugen is om een ​​pagina van de schijf te laden, worden oude, niet langer gebruikte pagina's uit het RAM-geheugen verwijderd.

De gegevens worden in de volgende vorm op schijf opgeslagen: voor elke partitie van elke cachegroep wordt een afzonderlijk bestand aangemaakt; in dit bestand verschijnen de pagina's één voor één in oplopende indexvolgorde. De volledige pagina-ID bevat de cachegroep-ID, het partitienummer en de pagina-index in het bestand. Met behulp van de volledige pagina-ID kunnen we dus voor elke pagina op unieke wijze het bestand en de offset in het bestand bepalen. Je kunt meer lezen over paginggeheugen in het Apache Ignite Wiki-artikel: Ontsteek Persistent Store - onder de motorkap.

Het compressiemechanisme voor schijfpagina's werkt, zoals u uit de naam kunt afleiden, op paginaniveau. Wanneer dit mechanisme is ingeschakeld, worden gegevens in het RAM verwerkt zoals ze zijn, zonder enige compressie, maar wanneer pagina's van RAM naar schijf worden opgeslagen, worden ze gecomprimeerd.

Maar het afzonderlijk comprimeren van elke pagina is geen oplossing voor het probleem; je moet op de een of andere manier de grootte van de resulterende gegevensbestanden verkleinen. Als de paginagrootte niet langer vaststaat, kunnen we niet langer pagina's achter elkaar naar het bestand schrijven, omdat dit een aantal problemen kan veroorzaken:

  • Met behulp van de pagina-index kunnen we de offset waarmee deze zich in het bestand bevindt niet berekenen.
  • Het is niet duidelijk wat te doen met pagina's die niet aan het einde van het bestand staan ​​en hun grootte wijzigen. Als het paginaformaat kleiner wordt, verdwijnt de vrijgekomen ruimte. Als het paginaformaat groter wordt, moet u daarvoor een nieuwe plaats in het bestand zoeken.
  • Als een pagina een aantal bytes verplaatst dat niet een veelvoud is van de blokgrootte van het bestandssysteem, dan zal het lezen of schrijven ervan een extra bestandssysteemblok vereisen, wat kan leiden tot prestatievermindering.

Om te voorkomen dat deze problemen op hun eigen niveau worden opgelost, gebruikt schijfpaginacompressie in Apache Ignite een bestandssysteemmechanisme dat sparse files wordt genoemd. Een sparse-bestand is een bestand waarin sommige met nul gevulde gebieden als "gaten" kunnen worden gemarkeerd. In dit geval worden er geen bestandssysteemblokken toegewezen om deze gaten op te slaan, wat resulteert in een besparing op schijfruimte.

Het is logisch dat om een ​​bestandssysteemblok vrij te maken, de grootte van het gat groter moet zijn dan of gelijk is aan het bestandssysteemblok, wat een extra beperking oplegt aan de paginagrootte en Apache Ignite: om compressie enig effect te laten hebben, de paginagrootte moet strikt groter zijn dan de grootte van het bestandssysteemblok. Als de paginagrootte gelijk is aan de blokgrootte, zullen we nooit een enkel blok kunnen vrijmaken, omdat om een ​​enkel blok vrij te maken, de gecomprimeerde pagina 0 bytes moet in beslag nemen. Als de paginagrootte gelijk is aan de grootte van 2 of 4 blokken, kunnen we al minstens één blok vrijmaken als onze pagina wordt gecomprimeerd tot respectievelijk minimaal 50% of 75%.

Dus de laatste beschrijving van hoe het mechanisme werkt: Bij het schrijven van een pagina naar schijf wordt een poging gedaan om de pagina te comprimeren. Als de grootte van de gecomprimeerde pagina het mogelijk maakt dat één of meer bestandssysteemblokken worden vrijgegeven, wordt de pagina in gecomprimeerde vorm geschreven en wordt er een “gat” gemaakt in plaats van de vrijgemaakte blokken (er wordt een systeemaanroep uitgevoerd fallocate() met de punchhole-vlag). Als de grootte van de gecomprimeerde pagina het niet toestaat dat de blokken worden vrijgegeven, wordt de pagina opgeslagen zoals deze is, ongecomprimeerd. Alle pagina-offsets worden op dezelfde manier berekend als zonder compressie, door de pagina-index te vermenigvuldigen met het paginaformaat. U hoeft zelf geen pagina's te verplaatsen. Pagina-offsets vallen, net als zonder compressie, op de grenzen van bestandssysteemblokken.

Gegevenscompressie in Apache Ignite. Ervaring van Sber

In de huidige implementatie kan Ignite alleen werken met verspreide bestanden onder Linux OS; dienovereenkomstig kan compressie van schijfpagina's alleen worden ingeschakeld bij gebruik van Ignite op dit besturingssysteem.

Compressie-algoritmen die kunnen worden gebruikt voor compressie van schijfpagina's: ZSTD, LZ4, Snappy. Daarnaast is er een bedieningsmodus (SKIP_GARBAGE), waarin alleen ongebruikte ruimte op de pagina wordt weggegooid zonder compressie toe te passen op de resterende gegevens, waardoor de belasting van de CPU wordt verminderd in vergelijking met de eerder genoemde algoritmen.

Prestatie-impact

Helaas heb ik geen daadwerkelijke prestatiemetingen op echte stands uitgevoerd, aangezien we niet van plan zijn dit mechanisme in de productie te gebruiken, maar we kunnen theoretisch speculeren waar we zullen verliezen en waar we zullen winnen.

Om dit te doen, moeten we onthouden hoe pagina's worden gelezen en geschreven wanneer ze worden geopend:

  • Bij het uitvoeren van een leesbewerking wordt eerst in het RAM gezocht; als de zoekopdracht niet succesvol is, wordt de pagina vanaf schijf in het RAM geladen door dezelfde thread die het lezen uitvoert.
  • Wanneer een schrijfbewerking wordt uitgevoerd, wordt de pagina in het RAM als vuil gemarkeerd, maar wordt de pagina niet onmiddellijk fysiek op schijf opgeslagen door de thread die het schrijven uitvoert. Alle vuile pagina's worden later in het controlepuntproces in afzonderlijke threads op schijf opgeslagen.

De impact op leesbewerkingen is dus:

  • Positief (schijf-IO), vanwege een afname van het aantal gelezen bestandssysteemblokken.
  • Negatief (CPU), vanwege de extra belasting die het besturingssysteem nodig heeft om met beperkte bestanden te werken. Het is ook mogelijk dat hier impliciet aanvullende IO-bewerkingen verschijnen om een ​​complexere sparse-bestandsstructuur op te slaan (helaas ben ik niet bekend met alle details van hoe sparse-bestanden werken).
  • Negatief (CPU), vanwege de noodzaak om pagina's te decomprimeren.
  • Er is geen impact op schrijfbewerkingen.
  • Impact op het controlepuntproces (alles lijkt hier op leesbewerkingen):
  • Positief (schijf-IO), vanwege een afname van het aantal geschreven bestandssysteemblokken.
  • Negatief (CPU, mogelijk schijf-IO), vanwege het werken met schaarse bestanden.
  • Negatief (CPU), vanwege de noodzaak van paginacompressie.

Aan welke kant van de schaal zal de schaal doorslaan? Dit hangt allemaal sterk af van de omgeving, maar ik ben geneigd te geloven dat compressie van schijfpagina's hoogstwaarschijnlijk zal leiden tot prestatievermindering op de meeste systemen. Bovendien laten tests op andere DBMS'en die een vergelijkbare aanpak gebruiken met verspreide bestanden een prestatiedaling zien wanneer compressie is ingeschakeld.

Hoe in te schakelen en te configureren

Zoals hierboven vermeld, is de minimale versie van Apache Ignite die schijfpaginacompressie ondersteunt 2.8 en wordt alleen het Linux-besturingssysteem ondersteund. Schakel en configureer als volgt:

  • Er moet een ontsteek-compressiemodule in het klassenpad aanwezig zijn. Standaard bevindt het zich in de Apache Ignite-distributie in de map libs/optioneel en is het niet opgenomen in het klassenpad. Je kunt de map eenvoudig één niveau omhoog verplaatsen naar libs en als je hem vervolgens via ignite.sh uitvoert, wordt deze automatisch ingeschakeld.
  • Persistentie moet zijn ingeschakeld (ingeschakeld via DataRegionConfiguration.setPersistenceEnabled(true)).
  • Het paginaformaat moet groter zijn dan de blokgrootte van het bestandssysteem (u kunt dit instellen met behulp van DataStorageConfiguration.setPageSize() ).
  • Voor elke cache waarvan de gegevens moeten worden gecomprimeerd, moet u de compressiemethode en (optioneel) het compressieniveau (methoden CacheConfiguration.setDiskPageCompression() , CacheConfiguration.setDiskPageCompressionLevel()).

WAL-verdichting

Hoe werkt dit

Wat is WAL en waarom is het nodig? Heel kort gezegd: dit is een log waarin alle gebeurtenissen staan ​​die uiteindelijk de pagina-opslag veranderen. Het is vooral nodig om te kunnen herstellen bij een val. Elke bewerking moet, voordat deze de controle aan de gebruiker geeft, eerst een gebeurtenis in WAL registreren, zodat deze in geval van een storing in het logboek kan worden afgespeeld en alle bewerkingen kan worden hersteld waarvoor de gebruiker een succesvol antwoord heeft ontvangen, zelfs als deze bewerkingen had geen tijd om weerspiegeld te worden in de pagina-opslag op schijf (al hierboven is beschreven dat het daadwerkelijke schrijven naar de pagina-opslag wordt gedaan in een proces dat "checkpointing" wordt genoemd, met enige vertraging door afzonderlijke threads).

Vermeldingen in WAL zijn onderverdeeld in logisch en fysiek. Booleaanse waarden zijn de sleutels en waarden zelf. Fysiek: weerspiegelt wijzigingen in pagina's in het paginaarchief. Hoewel logische records in sommige andere gevallen nuttig kunnen zijn, zijn fysieke records alleen nodig voor herstel in geval van een crash en zijn records alleen nodig sinds het laatste succesvolle controlepunt. Hier zullen we niet in detail treden en uitleggen waarom het op deze manier werkt, maar geïnteresseerden kunnen het reeds genoemde artikel op de Apache Ignite Wiki raadplegen: Ontsteek Persistent Store - onder de motorkap.

Vaak zijn er meerdere fysieke records per logisch record. Dat wil zeggen dat één invoerbewerking in de cache bijvoorbeeld meerdere pagina's in het paginageheugen beïnvloedt (een pagina met de gegevens zelf, pagina's met indexen, pagina's met vrije lijsten). Bij sommige synthetische tests ontdekte ik dat fysieke records tot 90% van het WAL-bestand in beslag namen. Ze zijn echter maar heel kort nodig (standaard is het interval tussen de controlepunten 3 minuten). Het zou logisch zijn om van deze gegevens af te komen nadat ze hun relevantie verloren hebben. Dit is precies wat het WAL-compressiemechanisme doet: het verwijdert fysieke records en comprimeert de resterende logische records met behulp van zip, terwijl de bestandsgrootte zeer aanzienlijk wordt verkleind (soms tientallen keren).

Fysiek bestaat WAL uit verschillende segmenten (standaard 10) met een vaste grootte (standaard 64 MB), die op een cirkelvormige manier worden overschreven. Zodra het huidige segment gevuld is, wordt het volgende segment als huidig ​​toegewezen en wordt het gevulde segment door een aparte thread naar het archief gekopieerd. WAL-verdichting werkt al met archiefsegmenten. Bovendien bewaakt het als aparte thread de uitvoering van het controlepunt en begint het met compressie in archiefsegmenten waarvoor fysieke records niet langer nodig zijn.

Gegevenscompressie in Apache Ignite. Ervaring van Sber

Prestatie-impact

Omdat WAL-verdichting als een aparte draad wordt uitgevoerd, mag er geen directe impact zijn op de uitgevoerde werkzaamheden. Maar het zorgt nog steeds voor extra achtergrondbelasting op de CPU (compressie) en schijf (het lezen van elk WAL-segment uit het archief en het schrijven van de gecomprimeerde segmenten), dus als het systeem op maximale capaciteit draait, zal dit ook leiden tot prestatievermindering.

Hoe in te schakelen en te configureren

U kunt WAL-verdichting inschakelen met behulp van de eigenschap WalCompactionEnabled в DataStorageConfiguration (DataStorageConfiguration.setWalCompactionEnabled(true)). Met de methode DataStorageConfiguration.setWalCompactionLevel() kunt u ook het compressieniveau instellen als u niet tevreden bent met de standaardwaarde (BEST_SPEED).

WAL-pagina snapshot-compressie

Hoe werkt dit

We hebben al ontdekt dat WAL-records zijn onderverdeeld in logisch en fysiek. Voor elke wijziging op elke pagina wordt een fysiek WAL-record gegenereerd in het paginageheugen. Fysieke records zijn op hun beurt ook onderverdeeld in 2 subtypen: page snapshot record en delta record. Elke keer dat we iets op een pagina wijzigen en deze van een schone naar een vuile staat overbrengen, wordt een volledige kopie van deze pagina opgeslagen in WAL (page snapshot record). Zelfs als we slechts één byte in WAL wijzigen, zal het record iets groter zijn dan de paginagrootte. Als we iets veranderen op een toch al vuile pagina, wordt er een deltarecord gevormd in WAL, dat alleen wijzigingen weergeeft in vergelijking met de vorige staat van de pagina, maar niet de hele pagina. Omdat het resetten van de status van pagina's van vuil naar schoon wordt uitgevoerd tijdens het controlepuntproces, zullen onmiddellijk na het begin van het controlepunt bijna alle fysieke records alleen bestaan ​​uit momentopnamen van pagina's (aangezien alle pagina's onmiddellijk na het begin van het controlepunt schoon zijn) , en als we het volgende controlepunt naderen, begint de deltarecordfractie te groeien en opnieuw te resetten aan het begin van het volgende controlepunt. Uit metingen in sommige synthetische tests bleek dat het aandeel pagina-snapshots in het totale volume aan fysieke records 90% bedraagt.

Het idee van WAL-pagina-snapshot-compressie is het comprimeren van pagina-snapshots met behulp van een kant-en-klare paginacompressietool (zie schijfpaginacompressie). Tegelijkertijd worden in WAL records opeenvolgend opgeslagen in de alleen-toevoegen-modus en is het niet nodig om records te binden aan de grenzen van bestandssysteemblokken. Hier hebben we dus, in tegenstelling tot het compressiemechanisme voor schijfpagina's, geen beperkte bestanden nodig op allemaal; dienovereenkomstig zal dit mechanisme niet alleen werken op het besturingssysteem Linux. Bovendien maakt het voor ons niet meer uit hoeveel we de pagina hebben kunnen comprimeren. Zelfs als we 1 byte hebben vrijgemaakt, is dit al een positief resultaat en kunnen we gecomprimeerde gegevens opslaan in WAL, in tegenstelling tot schijfpaginacompressie, waarbij we de gecomprimeerde pagina alleen opslaan als we meer dan 1 bestandssysteemblok hebben vrijgemaakt.

Pagina's zijn zeer comprimeerbare gegevens, hun aandeel in het totale WAL-volume is erg hoog, dus zonder het WAL-bestandsformaat te wijzigen kunnen we de omvang ervan aanzienlijk verkleinen. Compressie, inclusief logische records, zou een verandering in het formaat en verlies van compatibiliteit vereisen, bijvoorbeeld voor externe consumenten die mogelijk geïnteresseerd zijn in logische records, maar zou niet leiden tot een significante vermindering van de bestandsgrootte.

Net als bij schijfpaginacompressie kan WAL-pagina-snapshot-compressie gebruik maken van ZSTD-, LZ4- en Snappy-compressie-algoritmen, evenals de SKIP_GARBAGE-modus.

Prestatie-impact

Het is niet moeilijk om op te merken dat het direct inschakelen van WAL-pagina-snapshot-compressie alleen van invloed is op threads die gegevens naar het paginageheugen schrijven, dat wil zeggen op de threads die gegevens in caches wijzigen. Het lezen van fysieke records uit WAL gebeurt slechts één keer, op het moment dat het knooppunt omhoog gaat na een val (en alleen als het tijdens een controlepunt valt).

Dit heeft invloed op threads die gegevens op de volgende manier wijzigen: we krijgen een negatief effect (CPU) vanwege de noodzaak om de pagina elke keer te comprimeren voordat deze naar schijf wordt geschreven, en een positief effect (schijf-IO) vanwege een vermindering van de hoeveelheid gegevens geschreven. Dienovereenkomstig is alles hier eenvoudig: als de systeemprestaties worden beperkt door de CPU, krijgen we een lichte verslechtering, als deze wordt beperkt door schijf-I/O, krijgen we een toename.

Indirect heeft het verkleinen van de WAL-grootte ook een (positieve) invloed op stromen die WAL-segmenten in het archief dumpen en op WAL-verdichtingsstromen.

Echte prestatietests in onze omgeving met behulp van synthetische data lieten een lichte stijging zien (de doorvoer steeg met 10%-15%, de latentie daalde met 10%-15%).

Hoe in te schakelen en te configureren

Minimale Apache Ignite-versie: 2.8. Schakel en configureer als volgt:

  • Er moet een ontsteek-compressiemodule in het klassenpad aanwezig zijn. Standaard bevindt het zich in de Apache Ignite-distributie in de map libs/optioneel en is het niet opgenomen in het klassenpad. Je kunt de map eenvoudig één niveau omhoog verplaatsen naar libs en als je hem vervolgens via ignite.sh uitvoert, wordt deze automatisch ingeschakeld.
  • Persistentie moet zijn ingeschakeld (ingeschakeld via DataRegionConfiguration.setPersistenceEnabled(true)).
  • De compressiemodus moet worden ingesteld met behulp van de methode DataStorageConfiguration.setWalPageCompression(), is compressie standaard uitgeschakeld (modus UITGESCHAKELD).
  • Optioneel kunt u het compressieniveau instellen met behulp van de methode DataStorageConfiguration.setWalPageCompression()Zie de javadoc voor de methode voor geldige waarden voor elke modus.

Conclusie

De beschouwde datacompressiemechanismen in Apache Ignite kunnen onafhankelijk van elkaar worden gebruikt, maar elke combinatie hiervan is ook acceptabel. Als u begrijpt hoe ze werken, kunt u bepalen hoe geschikt ze zijn voor uw taken in uw omgeving en wat u moet opofferen bij het gebruik ervan. Compressie van schijfpagina's is ontworpen om de hoofdopslag te comprimeren en kan een gemiddelde compressieverhouding opleveren. WAL-pagina snapshot-compressie levert een gemiddelde mate van compressie op voor WAL-bestanden en zal hoogstwaarschijnlijk zelfs de prestaties verbeteren. WAL-compressie heeft geen positief effect op de prestaties, maar zal de grootte van WAL-bestanden zoveel mogelijk verkleinen door fysieke records te verwijderen.

Bron: www.habr.com

Voeg een reactie