Pozdrawiam, hab.
Jeśli ktoś wykorzystuje system
ClickHouse dobrze rozwiązuje opisane problemy. Przykładowo po przesłaniu 2TiB danych z szeptu mieszczą się one w 300GiB. Nie będę się rozpisywał szczegółowo nad porównaniem, artykułów na ten temat jest mnóstwo. Poza tym do niedawna nie wszystko układało się idealnie z naszą pamięcią masową ClickHouse.
Problemy z zajętą przestrzenią
Na pierwszy rzut oka wszystko powinno działać dobrze. Następny retention
), następnie utwórz tabelę zgodnie z rekomendacją wybranego backendu dla graphite-web:
Aby zrozumieć który trzeba wiedzieć jak działają wstawki i dalsza droga życia danych w tabelach silników rodziny *Połącz drzewo ClickHouse (wykresy zaczerpnięte z
- Wstawiono
блок
dane. W naszym przypadku pojawiły się metryki.
- Każdy taki blok przed zapisaniem na dysk jest sortowany według klucza.
ORDER BY
określone podczas tworzenia tabeli. - Po sortowaniu,
кусок
(part
) dane są zapisywane na dysku.
- Serwer monitoruje w tle, żeby takich fragmentów nie było zbyt wiele i uruchamia tło
слияния
(merge
, zwane dalej połączeniem).
- Serwer sam przestaje wykonywać operacje scalania, gdy tylko dane przestaną aktywnie napływać do pliku
партицию
(partition
), ale możesz uruchomić proces ręcznie za pomocą poleceniaOPTIMIZE
. - Jeśli na partycji pozostała tylko jedna część, nie będziesz mógł przeprowadzić scalania przy użyciu zwykłego polecenia; musisz użyć
OPTIMIZE ... FINAL
Tak więc pojawiają się pierwsze metryki. I zajmują trochę miejsca. Późniejsze wydarzenia mogą się nieco różnić w zależności od wielu czynników:
- Klucz partycjonowania może być bardzo mały (dzień) lub bardzo duży (kilka miesięcy).
- Konfiguracja przechowywania może pasować do kilku znaczących progów agregacji danych w ramach aktywnej partycji (gdzie rejestrowane są metryki), ale może nie.
- Jeśli danych jest dużo, to najwcześniejsze porcje, które ze względu na scalanie w tle mogą już być ogromne (jeśli wybierzesz nieoptymalny klucz partycjonowania), nie połączą się ze świeżymi małymi porcjami.
I zawsze kończy się tak samo. Miejsce zajmowane przez metryki w ClickHouse zwiększa się tylko wtedy, gdy:
- nie aplikuj
OPTIMIZE ... FINAL
ręcznie lub - nie wstawiaj na bieżąco danych do wszystkich partycji, bo prędzej czy później rozpocznie się scalanie w tle
Druga metoda wydaje się najłatwiejsza do wdrożenia, dlatego jest błędna i została wypróbowana jako pierwsza.
Napisałem dość prosty skrypt w Pythonie, który wysyłał fałszywe dane dla każdego dnia przez ostatnie 4 lata i uruchamiał cron co godzinę.
Ponieważ całe działanie ClickHouse DBMS opiera się na tym, że system ten prędzej czy później wykona całą pracę w tle, ale nie wiadomo kiedy, nie mogłem się doczekać momentu, w którym stare ogromne kawałki raczą zacząć się łączyć z nowe, małe. Stało się jasne, że musimy poszukać sposobu na zautomatyzowanie wymuszonych optymalizacji.
Informacje w tabelach systemu ClickHouse
Przyjrzyjmy się strukturze tabeli
- nazwa bazy danych (
database
); - Nazwa tabeli (
table
); - nazwa i identyfikator partycji (
partition
&partition_id
); - kiedy utwór powstał (
modification_time
); - minimalna i maksymalna data w sztuce (podział odbywa się według dni) (
min_date
&max_date
);
Jest też stół
- nazwa bazy danych (
Tables.database
); - Nazwa tabeli (
Tables.table
); - wiek metryczny, kiedy należy zastosować kolejną agregację (
age
);
Tak więc:
- Mamy tabelę fragmentów i tabelę reguł agregacji.
- Łączymy ich przecięcie i otrzymujemy wszystkie tabele *GraphiteMergeTree.
- Poszukujemy wszystkich przegród, w których:
- więcej niż jeden kawałek
- lub nadszedł czas na zastosowanie kolejnej reguły agregacji, oraz
modification_time
starsze niż ta chwila.
realizacja
Ta prośba
SELECT
concat(p.database, '.', p.table) AS table,
p.partition_id AS partition_id,
p.partition AS partition,
-- Самое "старое" правило, которое может быть применено для
-- партиции, но не в будущем, см (*)
max(g.age) AS age,
-- Количество кусков в партиции
countDistinct(p.name) AS parts,
-- За самую старшую метрику в партиции принимается 00:00:00 следующего дня
toDateTime(max(p.max_date + 1)) AS max_time,
-- Когда партиция должна быть оптимизированна
max_time + age AS rollup_time,
-- Когда самый старый кусок в партиции был обновлён
min(p.modification_time) AS modified_at
FROM system.parts AS p
INNER JOIN
(
-- Все правила для всех таблиц *GraphiteMergeTree
SELECT
Tables.database AS database,
Tables.table AS table,
age
FROM system.graphite_retentions
ARRAY JOIN Tables
GROUP BY
database,
table,
age
) AS g ON
(p.table = g.table)
AND (p.database = g.database)
WHERE
-- Только активные куски
p.active
-- (*) И только строки, где правила аггрегации уже должны быть применены
AND ((toDateTime(p.max_date + 1) + g.age) < now())
GROUP BY
table,
partition
HAVING
-- Только партиции, которые младше момента оптимизации
(modified_at < rollup_time)
-- Или с несколькими кусками
OR (parts > 1)
ORDER BY
table ASC,
partition ASC,
age ASC
zwraca każdą z partycji tabeli *GraphiteMergeTree, której połączenie powinno zwolnić miejsce na dysku. Pozostaje tylko przejrzeć je wszystkie z prośbą OPTIMIZE ... FINAL
. Ostateczna realizacja uwzględnia także fakt, że nie ma konieczności dotykania partycji z aktywnym zapisem.
Właśnie to ma na celu projekt
Jeśli uruchomisz program na serwerze z ClickHouse, po prostu zacznie on działać w trybie demona. Raz na godzinę będzie wykonywane żądanie sprawdzające, czy pojawiły się nowe partycje starsze niż trzy dni, które można zoptymalizować.
Nasze najbliższe plany to dostarczenie co najmniej pakietów deb, a jeśli to możliwe, także pakietów RPM.
Zamiast zawierania
Przez ostatnie ponad 9 miesięcy pracowałem w mojej firmie
Kilka litrów piwa i dni administracyjnych poświęcono na opracowanie wniosku wraz z
Źródło: www.habr.com