Kompresja danych w Apache Ignite. Doświadczenia Sbera

Kompresja danych w Apache Ignite. Doświadczenia SberaPodczas pracy z dużymi wolumenami danych czasami może pojawić się problem braku miejsca na dysku. Jednym ze sposobów rozwiązania tego problemu jest kompresja, dzięki której na tym samym sprzęcie można sobie pozwolić na zwiększenie objętości pamięci. W tym artykule przyjrzymy się, jak działa kompresja danych w Apache Ignite. W tym artykule opisano jedynie metody kompresji dysku zaimplementowane w produkcie. Inne metody kompresji danych (przez sieć, w pamięci), niezależnie od tego, czy zostały wdrożone, czy nie, pozostaną poza zakresem.

Zatem przy włączonym trybie trwałości, w wyniku zmian danych w pamięciach podręcznych, Ignite zaczyna zapisywać na dysk:

  1. Zawartość skrzynek
  2. Zapis dziennika z wyprzedzeniem (zwany dalej po prostu WAL)

Już od dłuższego czasu istnieje mechanizm kompresji WAL, zwany kompresją WAL. Niedawno wydany Apache Ignite 2.8 wprowadził dwa dodatkowe mechanizmy umożliwiające kompresję danych na dysku: kompresję strony dysku do kompresji zawartości pamięci podręcznych oraz kompresję migawek strony WAL do kompresji niektórych wpisów WAL. Więcej szczegółów na temat wszystkich trzech mechanizmów poniżej.

Kompresja strony dysku

Jak to działa

Najpierw przyjrzyjmy się pokrótce, w jaki sposób Ignite przechowuje dane. Pamięć strony służy do przechowywania. Rozmiar strony jest ustawiany na początku węzła i nie można go zmienić na późniejszych etapach; ponadto rozmiar strony musi być potęgą dwójki i wielokrotnością rozmiaru bloku systemu plików. Strony ładowane są do pamięci RAM z dysku w miarę potrzeb; rozmiar danych na dysku może przekraczać ilość przydzielonej pamięci RAM. Jeśli w pamięci RAM nie ma wystarczającej ilości miejsca, aby załadować stronę z dysku, stare, nieużywane strony zostaną usunięte z pamięci RAM.

Dane zapisywane są na dysku w następującej formie: dla każdej partycji każdej grupy pamięci podręcznej tworzony jest oddzielny plik, w którym strony pojawiają się jedna po drugiej w rosnącej kolejności indeksów. Identyfikator pełnej strony zawiera identyfikator grupy pamięci podręcznej, numer partycji i indeks strony w pliku. Zatem korzystając z identyfikatora pełnej strony, możemy jednoznacznie określić plik i przesunięcie w pliku dla każdej strony. Więcej informacji na temat pamięci stronicowania można znaleźć w artykule Apache Ignite Wiki: Ignite Persistent Store - pod maską.

Mechanizm kompresji stron dysku, jak można się domyślić z nazwy, działa na poziomie strony. Gdy ten mechanizm jest włączony, dane w pamięci RAM są przetwarzane w niezmienionej postaci, bez jakiejkolwiek kompresji, ale podczas zapisywania stron z pamięci RAM na dysk są one kompresowane.

Jednak osobna kompresja każdej strony nie rozwiązuje problemu; należy w jakiś sposób zmniejszyć rozmiar wynikowych plików danych. Jeśli rozmiar strony nie jest już stały, nie możemy już zapisywać stron do pliku jedna po drugiej, ponieważ może to spowodować szereg problemów:

  • Korzystając z indeksu strony, nie będziemy w stanie obliczyć przesunięcia, według którego znajduje się on w pliku.
  • Nie jest jasne, co zrobić ze stronami, które nie znajdują się na końcu pliku i zmieniają swój rozmiar. Jeśli rozmiar strony się zmniejszy, zwolniona przez nią przestrzeń zniknie. Jeśli rozmiar strony się zwiększy, należy poszukać dla niej nowego miejsca w pliku.
  • Jeśli strona przesunie się o liczbę bajtów, która nie jest wielokrotnością rozmiaru bloku systemu plików, wówczas odczyt lub zapis będzie wymagał dotknięcia jeszcze jednego bloku systemu plików, co może prowadzić do pogorszenia wydajności.

Aby uniknąć rozwiązywania tych problemów na własnym poziomie, kompresja stron dysku w Apache Ignite wykorzystuje mechanizm systemu plików zwany plikami rozrzedzonymi. Plik rozrzedzony to taki, w którym niektóre obszary wypełnione zerami można oznaczyć jako „dziury”. W takim przypadku żadne bloki systemu plików nie zostaną przydzielone do przechowywania tych dziur, co spowoduje oszczędność miejsca na dysku.

Logiczne jest, że aby zwolnić blok systemu plików, rozmiar dziury musi być większy lub równy blokowi systemu plików, co nakłada dodatkowe ograniczenie na rozmiar strony i Apache Ignite: aby kompresja miała jakikolwiek skutek, rozmiar strony musi być ściśle większy niż rozmiar bloku systemu plików. Jeśli rozmiar strony jest równy rozmiarowi bloku, to nigdy nie będziemy mogli zwolnić pojedynczego bloku, ponieważ aby zwolnić pojedynczy blok, skompresowana strona musi zajmować 0 bajtów. Jeśli rozmiar strony będzie równy rozmiarowi 2 lub 4 bloków, będziemy mogli już zwolnić przynajmniej jeden blok, jeśli nasza strona zostanie skompresowana odpowiednio do co najmniej 50% lub 75%.

Tym samym ostateczny opis działania mechanizmu: Podczas zapisywania strony na dysk podejmowana jest próba skompresowania strony. Jeżeli rozmiar skompresowanej strony pozwala na zwolnienie jednego lub więcej bloków systemu plików, wówczas strona jest zapisywana w formie skompresowanej, a w miejscu zwolnionych bloków wykonywana jest „dziura” (wykonywane jest wywołanie systemowe fallocate() z flagą dziurkacza). Jeśli rozmiar skompresowanej strony nie pozwala na uwolnienie bloków, strona zostanie zapisana w niezmienionej postaci, nieskompresowana. Wszystkie przesunięcia stron są obliczane w taki sam sposób, jak bez kompresji, poprzez pomnożenie indeksu strony przez rozmiar strony. Nie jest wymagane samodzielne przenoszenie stron. Przesunięcia stron, podobnie jak bez kompresji, mieszczą się w granicach bloków systemu plików.

Kompresja danych w Apache Ignite. Doświadczenia Sbera

W bieżącej implementacji Ignite może działać tylko z rzadkimi plikami w systemie operacyjnym Linux, dlatego też kompresję stron dysku można włączyć tylko w przypadku korzystania z Ignite w tym systemie operacyjnym.

Algorytmy kompresji, które można wykorzystać do kompresji strony dysku: ZSTD, LZ4, Snappy. Dodatkowo istnieje tryb pracy (SKIP_GARBAGE), w którym wyrzucane jest jedynie niewykorzystane miejsce na stronie bez stosowania kompresji na pozostałych danych, co zmniejsza obciążenie procesora w porównaniu do wcześniej wymienionych algorytmów.

Wpływ na wydajność

Niestety nie przeprowadziłem rzeczywistych pomiarów wydajności na rzeczywistych stojakach, gdyż nie planujemy stosowania tego mechanizmu w produkcji, ale teoretycznie możemy spekulować, gdzie przegramy, a gdzie wygramy.

Aby to zrobić, musimy pamiętać, w jaki sposób strony są odczytywane i zapisywane podczas uzyskiwania dostępu:

  • Podczas wykonywania operacji odczytu jest ona najpierw przeszukiwana w pamięci RAM; jeżeli wyszukiwanie zakończy się niepowodzeniem, strona jest ładowana do pamięci RAM z dysku przez ten sam wątek, który dokonuje odczytu.
  • Kiedy wykonywana jest operacja zapisu, strona w pamięci RAM jest oznaczana jako zanieczyszczona, ale strona nie jest natychmiast fizycznie zapisywana na dysku przez wątek wykonujący zapis. Wszystkie brudne strony są zapisywane na dysku później w procesie punktu kontrolnego w oddzielnych wątkach.

Zatem wpływ na operacje odczytu jest następujący:

  • Pozytywny (dysk IO), ze względu na zmniejszenie liczby odczytanych bloków systemu plików.
  • Ujemny (CPU), ze względu na dodatkowe obciążenie wymagane przez system operacyjny do pracy z rzadkimi plikami. Możliwe jest również, że domyślnie pojawią się tutaj dodatkowe operacje IO, aby zapisać bardziej złożoną strukturę plików rzadkich (niestety nie znam wszystkich szczegółów działania plików rzadkich).
  • Negatywne (CPU), ze względu na konieczność dekompresji stron.
  • Nie ma to wpływu na operacje zapisu.
  • Wpływ na proces punktu kontrolnego (wszystko tutaj przypomina operacje odczytu):
  • Pozytywny (dysk IO), ze względu na zmniejszenie liczby zapisywanych bloków systemu plików.
  • Ujemny (procesor, prawdopodobnie dyskowe wejście/wyjście) z powodu pracy z rzadkimi plikami.
  • Negatywne (CPU), ze względu na konieczność kompresji strony.

Która strona skali przechyli szalę? Wszystko w dużej mierze zależy od środowiska, ale jestem skłonny wierzyć, że kompresja stron dysku najprawdopodobniej doprowadzi do pogorszenia wydajności w większości systemów. Co więcej, testy na innych systemach DBMS, które stosują podobne podejście do plików rzadkich, wykazują spadek wydajności po włączeniu kompresji.

Jak włączyć i skonfigurować

Jak wspomniano powyżej, minimalna wersja Apache Ignite obsługująca kompresję stron dysku to 2.8 i obsługiwany jest tylko system operacyjny Linux. Włącz i skonfiguruj w następujący sposób:

  • W ścieżce klasy musi znajdować się moduł kompresji ignite. Domyślnie znajduje się on w dystrybucji Apache Ignite w katalogu libs/opcjonalny i nie jest uwzględniony w ścieżce klasy. Możesz po prostu przenieść katalog o jeden poziom wyżej do libs, a kiedy uruchomisz go przez ignite.sh, zostanie on automatycznie włączony.
  • Trwałość musi być włączona (Włączone przez DataRegionConfiguration.setPersistenceEnabled(true)).
  • Rozmiar strony musi być większy niż rozmiar bloku systemu plików (można go ustawić za pomocą DataStorageConfiguration.setPageSize() ).
  • Dla każdej pamięci podręcznej, której dane wymagają kompresji, należy skonfigurować metodę kompresji i (opcjonalnie) poziom kompresji (metody CacheConfiguration.setDiskPageCompression() , CacheConfiguration.setDiskPageCompressionLevel()).

Zagęszczanie WAL

Jak to działa

Co to jest WAL i dlaczego jest potrzebny? W skrócie: jest to dziennik zawierający wszystkie zdarzenia, które ostatecznie zmieniają miejsce przechowywania strony. Jest potrzebny przede wszystkim po to, aby móc zregenerować się w razie upadku. Jakakolwiek operacja, zanim odda kontrolę użytkownikowi, musi najpierw zarejestrować zdarzenie w WAL, aby w przypadku awarii można było ją odtworzyć w logu i przywrócić wszystkie operacje, na które użytkownik otrzymał pomyślną odpowiedź, nawet jeśli te operacje nie miał czasu na odzwierciedlenie w zapisie strony na dysku (już powyżej opisano, że faktyczny zapis do magazynu strony odbywa się w procesie zwanym „punktem kontrolnym” z pewnym opóźnieniem przez oddzielne wątki).

Wpisy w WAL dzielą się na logiczne i fizyczne. Wartości logiczne są same w sobie kluczami i wartościami. Fizyczne — odzwierciedla zmiany na stronach w magazynie stron. Chociaż zapisy logiczne mogą być przydatne w niektórych innych przypadkach, zapisy fizyczne są potrzebne tylko do odzyskania danych w przypadku awarii, a zapisy są potrzebne tylko od ostatniego pomyślnego punktu kontrolnego. Nie będziemy tutaj wchodzić w szczegóły i wyjaśniać dlaczego to działa w ten sposób, ale zainteresowanych odsyłam do wspomnianego już artykułu na Apache Ignite Wiki: Ignite Persistent Store - pod maską.

Często na rekord logiczny przypada kilka rekordów fizycznych. Oznacza to, że na przykład jedna operacja umieszczania w pamięci podręcznej wpływa na kilka stron w pamięci strony (strona z samymi danymi, strony z indeksami, strony z wolnymi listami). W niektórych testach syntetycznych odkryłem, że zapisy fizyczne zajmowały do ​​90% pliku WAL. Są one jednak potrzebne na bardzo krótki czas (domyślnie odstęp między punktami kontrolnymi wynosi 3 minuty). Logiczne byłoby pozbycie się tych danych po utracie ich przydatności. Dokładnie to robi mechanizm kompresji WAL: pozbywa się rekordów fizycznych i kompresuje pozostałe rekordy logiczne za pomocą zip, przy czym rozmiar pliku jest bardzo znacznie zmniejszany (czasami dziesiątki razy).

Fizycznie WAL składa się z kilku segmentów (domyślnie 10) o stałym rozmiarze (domyślnie 64 MB), które są nadpisywane cyklicznie. Gdy tylko bieżący segment zostanie zapełniony, następny segment zostaje przypisany jako bieżący, a wypełniony segment jest kopiowany do archiwum osobnym wątkiem. Kompaktowanie WAL działa już z segmentami archiwalnymi. Ponadto jako osobny wątek monitoruje wykonanie punktu kontrolnego i rozpoczyna kompresję w segmentach archiwum, dla których fizyczne zapisy nie są już potrzebne.

Kompresja danych w Apache Ignite. Doświadczenia Sbera

Wpływ na wydajność

Ponieważ zagęszczanie WAL przebiega jako oddzielny wątek, nie powinno mieć to bezpośredniego wpływu na wykonywane operacje. Jednak nadal powoduje to dodatkowe obciążenie procesora (kompresja) i dysku (odczyt każdego segmentu WAL z archiwum i zapisanie skompresowanych segmentów), więc jeśli system działa z maksymalną wydajnością, doprowadzi to również do pogorszenia wydajności.

Jak włączyć i skonfigurować

Za pomocą tej właściwości można włączyć zagęszczanie WAL WalCompactionEnabled в DataStorageConfiguration (DataStorageConfiguration.setWalCompactionEnabled(true)). Ponadto za pomocą metody DataStorageConfiguration.setWalCompactionLevel() można ustawić poziom kompresji, jeśli wartość domyślna (BEST_SPEED) nie jest dla Ciebie satysfakcjonująca.

Kompresja migawki strony WAL

Jak to działa

Dowiedzieliśmy się już, że w WAL rekordy dzielą się na logiczne i fizyczne. Dla każdej zmiany na każdej stronie generowany jest fizyczny rekord WAL w pamięci strony. Rekordy fizyczne z kolei dzielą się także na 2 podtypy: zapis migawki strony i rekord delta. Za każdym razem, gdy zmieniamy coś na stronie i przenosimy ją ze stanu czystego do stanu brudnego, pełna kopia tej strony jest przechowywana w WAL (rekord migawki strony). Nawet jeśli zmienimy tylko jeden bajt w WAL, rekord będzie nieco większy niż rozmiar strony. Jeśli zmienimy coś na już brudnej stronie, w WAL powstaje rekord delta, który odzwierciedla tylko zmiany w porównaniu do poprzedniego stanu strony, ale nie całej strony. Ponieważ resetowanie stanu stron z brudnych do czystych odbywa się podczas procesu punktu kontrolnego, natychmiast po rozpoczęciu punktu kontrolnego prawie wszystkie zapisy fizyczne będą składać się wyłącznie z migawek stron (ponieważ wszystkie strony bezpośrednio po rozpoczęciu punktu kontrolnego są czyste) , następnie w miarę zbliżania się do następnego punktu kontrolnego część rekordu delta zaczyna rosnąć i ponownie resetuje się na początku następnego punktu kontrolnego. Pomiary w niektórych testach syntetycznych wykazały, że udział migawek stron w całkowitej objętości zapisów fizycznych sięga 90%.

Ideą kompresji migawek stron WAL jest kompresowanie migawek stron za pomocą gotowego narzędzia do kompresji stron (patrz kompresja stron dyskowych). Jednocześnie w WAL rekordy zapisywane są sekwencyjnie w trybie tylko do dołączania i nie ma potrzeby wiązania rekordów z granicami bloków systemu plików, więc tutaj, w przeciwieństwie do mechanizmu kompresji stron dysku, nie potrzebujemy plików rozrzedzonych na all; w związku z tym mechanizm ten będzie działał nie tylko w systemie operacyjnym Linux. Poza tym nie ma już dla nas znaczenia, jak bardzo udało nam się skompresować stronę. Nawet jeśli zwolniliśmy 1 bajt, to już jest to wynik pozytywny i możemy zapisać skompresowane dane w WAL, w przeciwieństwie do kompresji strony dysku, gdzie zapisujemy skompresowaną stronę tylko wtedy, gdy zwolniliśmy więcej niż 1 blok systemu plików.

Strony są danymi wysoce ściśliwymi, ich udział w całkowitym wolumenie WAL jest bardzo duży, zatem bez zmiany formatu pliku WAL możemy uzyskać znaczne zmniejszenie jego rozmiaru. Kompresja, w tym rekordów logicznych, wymagałaby zmiany formatu i utraty kompatybilności, na przykład dla odbiorców zewnętrznych, którzy mogą być zainteresowani rekordami logicznymi, ale nie prowadziłaby do znacznego zmniejszenia rozmiaru pliku.

Podobnie jak w przypadku kompresji stron dysku, kompresja migawek stron WAL może wykorzystywać algorytmy kompresji ZSTD, LZ4, Snappy, a także tryb SKIP_GARBAGE.

Wpływ na wydajność

Nietrudno zauważyć, że bezpośrednie włączenie kompresji migawek stron WAL wpływa tylko na wątki zapisujące dane do pamięci strony, czyli te wątki, które zmieniają dane w pamięciach podręcznych. Odczyt zapisów fizycznych z WAL następuje tylko raz, w momencie podniesienia węzła po upadku (i tylko w przypadku upadku w punkcie kontrolnym).

Wpływa to na wątki zmieniające dane w następujący sposób: efekt negatywny (CPU) uzyskujemy ze względu na konieczność każdorazowej kompresji strony przed zapisem na dysk oraz efekt pozytywny (IO dysku) ze względu na zmniejszenie ilości zapisane dane. W związku z tym wszystko jest tutaj proste: jeśli wydajność systemu jest ograniczona przez procesor, otrzymamy niewielką degradację, jeśli jest ograniczona przez wejścia/wyjścia dysku, otrzymamy wzrost.

Pośrednio zmniejszenie rozmiaru WAL wpływa również (pozytywnie) na strumienie, które zrzucają segmenty WAL do strumieni archiwalnych i kompresji WAL.

Rzeczywiste testy wydajności w naszym środowisku z wykorzystaniem danych syntetycznych wykazały niewielki wzrost (przepustowość wzrosła o 10%-15%, opóźnienia spadły o 10%-15%).

Jak włączyć i skonfigurować

Minimalna wersja Apache Ignite: 2.8. Włącz i skonfiguruj w następujący sposób:

  • W ścieżce klasy musi znajdować się moduł kompresji ignite. Domyślnie znajduje się on w dystrybucji Apache Ignite w katalogu libs/opcjonalny i nie jest uwzględniony w ścieżce klasy. Możesz po prostu przenieść katalog o jeden poziom wyżej do libs, a kiedy uruchomisz go przez ignite.sh, zostanie on automatycznie włączony.
  • Trwałość musi być włączona (Włączone przez DataRegionConfiguration.setPersistenceEnabled(true)).
  • Tryb kompresji należy ustawić za pomocą metody DataStorageConfiguration.setWalPageCompression(), kompresja jest domyślnie wyłączona (tryb WYŁĄCZONY).
  • Opcjonalnie za pomocą metody można ustawić poziom kompresji DataStorageConfiguration.setWalPageCompression(), zobacz javadoc dla metody dla prawidłowych wartości dla każdego trybu.

wniosek

Rozważane mechanizmy kompresji danych w Apache Ignite można stosować niezależnie od siebie, ale dopuszczalna jest także dowolna ich kombinacja. Zrozumienie ich działania pozwoli Ci określić, jak bardzo nadają się do Twoich zadań w Twoim środowisku i co będziesz musiał poświęcić, korzystając z nich. Kompresja stron dysku ma na celu kompresję pamięci głównej i może zapewnić średni współczynnik kompresji. Kompresja migawki strony WAL zapewni średni stopień kompresji plików WAL i najprawdopodobniej nawet poprawi wydajność. Kompaktowanie WAL nie będzie miało pozytywnego wpływu na wydajność, ale maksymalnie zmniejszy rozmiar plików WAL poprzez usunięcie zapisów fizycznych.

Źródło: www.habr.com

Dodaj komentarz