ClickHouse + Graphite: jak znacznie zmniejszyć zajmowaną przestrzeń na dyskach

ClickHouse + Graphite: jak znacznie zmniejszyć zajmowaną przestrzeń na dyskach

Witaj, habr.

Jeśli ktoś wykorzystuje system graphite-web i napotyka problemy z wydajnością magazynu whisper (IO, zajmowane miejsce na dysku), to szansa, że rozważano ClickHouse jako alternatywę, powinna zmierzać do jedności. To stwierdzenie implikuje, że jako demon przyjmujący metryki już używana jest zewnętrzna implementacja, na przykład carbonwriter lub go-carbon.

ClickHouse dobrze radzi sobie z opisanymi problemami. Na przykład, po przelaniu 2TiB danych z whisper, zmieściły się one w 300GiB. Nie będę się zatrzymywał na szczegółowej analizie porównawczej, materiałów na ten temat jest wystarczająco dużo. Co więcej, do niedawna nasze magazynowanie ClickHouse nie było idealne.

Problemy z zajmowanym miejscem

Na pierwszy rzut oka, wszystko powinno działać dobrze. Podążając dokumentacji, tworzymy konfigurację dla schemy przechowywania metryk (dalej retention), następnie tworzymy tabelę zgodnie z zaleceniami wybranego backendu dla graphite-web: carbon-clickhouse+graphite-clickhouse lub graphouse, w zależności od używanego stosu. I... włącza się bomba zegarowa.

Aby zrozumieć, jaka to bomba, trzeba znać, jak działają wstawienia i dalsza życiowa droga danych w tabelach silników z rodziny *MergeTree ClickHouse (diagramy pochodzą z prezentacji Aleksieja Zatelepińa):

  • Wstawiane są bloki danych. W naszym przypadku to przyleciały metryki.
    ClickHouse + Graphite: jak znacznie zmniejszyć zajmowaną przestrzeń na dyskach
  • Każdy taki blok przed zapisem na dysku jest sortowany zgodnie z kluczem ORDER BY, podanym podczas tworzenia tabeli.
  • Po sortowaniu, kawałek (part) danych jest zapisywany na dysku.
    ClickHouse + Graphite: jak znacznie zmniejszyć zajmowaną przestrzeń na dyskach
  • Serwer w tle monitoruje, aby takich kawałków nie było za dużo, i uruchamia procesy łączenia (merge, następnie mergi.
    ClickHouse + Graphite: jak znacznie zmniejszyć zajmowaną przestrzeń na dyskach
    ClickHouse + Graphite: jak znacznie zmniejszyć zajmowaną przestrzeń na dyskach
  • Serwer przestaje samodzielnie uruchamiać mergi, gdy dane przestają aktywnie napływać do partycji (partition), ale można uruchomić proces ręcznie komendą OPTIMIZE.
  • Jeśli w partycji pozostał tylko jeden kawałek, to nie będzie możliwe uruchomienie mergi zwykłą komendą, należy użyć OPTIMIZE ... FINAL

Tak więc, przychodzą pierwsze metryki. I zajmują pewne miejsce. Kolejne zdarzenia mogą się nieco różnić w zależności od wielu czynników:

  • Klucz partycjonowania może być zarówno bardzo mały (dzień), jak i bardzo duży (kilka miesięcy).
  • Konfiguracja retention może obejmować kilka znaczących progów agregacji danych w aktywnej partycji (gdzie zapisywane są metryki), ale nie musi.
  • Jeśli danych jest bardzo dużo, to najwcześniejsze kawałki, które ze względu na tło merge mogą być już ogromne (przy wyborze nieoptymalnego klucza partycjonowania), nie będą się same łączyć z nowymi małymi kawałkami.

I zawsze kończy się to tak samo. Miejsce zajmowane przez metryki w ClickHouse tylko rośnie, jeśli:

  • nie stosuję OPTIMIZE ... FINAL ręcznie lub
  • nie wstawiam danych do wszystkich partycji na stałe, aby prędzej czy później uruchomić tło merge

Drugi sposób wydaje się najprostszy do wdrożenia i dlatego jest błędny i był testowany w pierwszej kolejności.
Napisałem wystarczająco prosty skrypt w pythonie, który wysyłał fikcyjne metryki dla każdego dnia przez ostatnie 4 lata i był uruchamiany co godzinę przez cron.
Ponieważ cała praca ClickHouse DBMS oparta jest na tym, że ten system prędzej czy później wykona całą pracę w tle, ale nie wiadomo kiedy, nie udało mi się doczekać momentu, kiedy stare ogromne kawałki będą mogły zacząć łączyć się z nowymi małymi. Zrozumiałem, że muszę znaleźć sposób na zautomatyzowanie wymuszonej optymalizacji.

ClickHouse + Graphite: jak znacznie zmniejszyć zajmowaną przestrzeń na dyskach

Informacje w tabelach systemowych ClickHouse

Przyjrzyjmy się strukturze tabeli system.parts. To wyczerpujące informacje o każdym kawałku wszystkich tabel na serwerze ClickHouse. Zawiera między innymi następujące kolumny:

  • nazwa bazy danych (database);
  • nazwa tabeli (stół);
  • nazwa i ID partycji (partition & partition_id);
  • kiedy kawałek został stworzony (modification_time);
  • minimalna i maksymalna data w kawałku (partycjonowanie odbywa się według dni) (min_date & max_date);

Jest też tabela system.graphite_retentions, z następującymi interesującymi polami:

  • nazwa bazy danych (Tables.database);
  • nazwa tabeli (Tables.table);
  • wiek metryki, kiedy ma być zastosowana następna agregacja (age);

A zatem:

  1. Mamy tabelę kawałków i tabelę reguł agregacji.
  2. Łączymy ich przekrój i otrzymujemy wszystkie tabele *GraphiteMergeTree.
  3. Szukamy wszystkich partycji, w których:
    • więcej niż jeden kawałek
    • lub nadszedł moment, aby zastosować następną regułę agregacji, i modification_time jest starsze od tego momentu.

Realizacja

To zapytanie

WYBIERZ
    concat(p.database, '.', p.table) AS table,
    p.partition_id AS partition_id,
    p.partition AS partition,
    -- Najstarsza "zasada", która może być stosowana do
    -- partycji, ale nie w przyszłości, zobacz (*)
    max(g.age) AS age,
    -- Liczba części w partycji
    countDistinct(p.name) AS parts,
    -- Najstarsza metryka w partycji uznawana jest za 00:00:00 następnego dnia
    toDateTime(max(p.max_date + 1)) AS max_time,
    -- Kiedy partycja powinna być zoptymalizowana
    max_time + age AS rollup_time,
    -- Kiedy najstarszy fragment w partycji był aktualizowany
    min(p.modification_time) AS modified_at
Z system.parts AS p
INNER JOIN
(
    -- Wszystkie zasady dla wszystkich tabel *GraphiteMergeTree
    WYBIERZ
        Tables.database AS database,
        Tables.table AS table,
        age
    Z system.graphite_retentions
    ARRAY JOIN Tables
    GRUPUJ PO
        database,
        table,
        age
) AS g ON
    (p.table = g.table)
    I (p.database = g.database)
GDZIE
    -- Tylko aktywne fragmenty
    p.active
    -- (*) I tylko wiersze, gdzie zasady agregacji już powinny być stosowane
    I ((toDateTime(p.max_date + 1) + g.age) < now())
GRUPUJ PO
    table,
    partition
HAVING
    -- Tylko partycje, które są młodsze od momentu optymalizacji
    (modified_at  1)
ZAMÓW
    table ASC,
    partition ASC,
    age ASC

zwraca każdą z partycji tabel *GraphiteMergeTree, których połączenie powinno prowadzić do uwolnienia miejsca na dysku. Pozostaje jedynie kwestia: przejść przez nie wszystkie za pomocą zapytania OPTIMIZE ... FINAL. W finalnej wersji uwzględniono również to, że partycje z aktywnym zapisem nie muszą być ruszane.

Właśnie to czyni projekt graphite-ch-optimizer. Byli koledzy z Yandex.Market przetestowali go w produkcji, efekty pracy można zobaczyć poniżej.

ClickHouse + Graphite: jak znacznie zmniejszyć zajmowaną przestrzeń na dyskach

Jeśli uruchomisz program na serwerze z ClickHouse, po prostu zacznie działać w trybie demona. Co godzinę będzie wykonywane zapytanie, sprawdzając, czy nie pojawiły się nowe partycje starsze niż trzy dni, które można zoptymalizować.

W najbliższych planach — dostarczenie przynajmniej pakietów deb, a jeśli to możliwe — także rpm.

Zamiast zakończenia

W ciągu ostatnich 9 miesięcy spędziłem dużo czasu w mojej firmie InnoGames mieszając ClickHouse z graphite-web. To było dobre doświadczenie, którego wynikiem może być szybkie przejście z whisper na ClickHouse jako magazyn metryk. Mam nadzieję, że ten artykuł będzie czymś w rodzaju początku cyklu o tym, jakie ulepszenia wnieśliśmy w różne części tego stosu i co będzie zrobione w przyszłości.

Na opracowanie zapytania poświęcono kilka litrów piwa i dni administracyjne wspólnie z v0devil, za co pragnę wyrazić mu swoją wdzięczność. A także za recenzję tego artykułu.

Strona projektu na github

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster