Вітаю, habr.
Калі нехта эксплуатуе сістэму
ClickHouse добра вырашае апісаныя праблемы. Да прыкладу, пасля пераліўкі 2TiB дадзеных з whisper, яны змясціліся ў 300GiB. Падрабязна на параўнанні я спыняцца не буду, артыкулаў на гэтую тэму хапае. Да таго ж, да нядаўняга часу з нашым ClickHouse сховішчам было не ўсё ідэальна.
Праблемы з спажываным месцам
На першы погляд усё павінна працаваць добра. Прытрымліваючыся retention
), затым ствараем табліцу паводле рэкамендацыі абранага бэкенда для graphite-web:
Для таго, каб зразумець, якая, трэба ведаць, як працуюць устаўкі і далейшы жыццёвы шлях дадзеных у табліцах рухавікоў сямейства *MergeTree ClickHouse (дыяграмы ўзяты з
- Устаўляецца
блок
дадзеных. У нашым выпадку гэта прыляцелі метрыкі.
- Кожны такі блок перад запісам на дыск сартуецца паводле ключа.
ORDER BY
, паказанаму пры стварэнні табліцы. - Пасля сартавання,
кусок
(part
) дадзеных запісваецца на дыск.
- Сервер сочыць у фоне, каб такіх кавалкаў было не шмат, і запускае фонавыя.
слияния
(merge
, далей мержы).
- Сервер перастае запускаць мержы самастойна, як толькі дадзеныя перастаюць актыўна паступаць у
партицию
(partition
), але можна запусціць працэс уручную камандайOPTIMIZE
. - Калі ў партіціі застаўся толькі адзін кавалак, то запусціць мерж звычайнай камандай не атрымаецца, неабходна выкарыстоўваць
OPTIMIZE ... FINAL
Такім чынам, паступаюць першыя метрыкі. І яны займаюць нейкую прастору. Наступныя падзеі могуць некалькі вар'іравацца ў залежнасці ад шматлікіх фактараў:
- Ключ партыцыянавання можа быць як вельмі маленькім (дзень), так і вельмі вялікім (некалькі месяцаў).
- Канфіг retention можа змяшчаць некалькі значных парогаў агрэгацыі дадзеных усярэдзіне актыўнай партіціі (куды ідзе запіс метрык), а можа і не.
- Калі дадзеных вельмі шмат, то самыя раннія кавалкі, якія з-за фонавых мержаў могуць ужо быць вялізнымі (пры выбары неаптымальнага ключа партыцыянавання), не будуць мержаться самі са свежымі маленькімі кавалкамі.
І заканчваецца заўсёды ўсё аднолькава. Месца, займанае метрыкамі ў ClickHouse толькі расце, калі:
- не прымяняць
OPTIMIZE ... FINAL
уручную або - не ўстаўляць дадзеныя ва ўсе партіціі на пастаяннай аснове, каб рана ці позна запусціць фонавы мерж
Другі спосаб здаецца найболей простым у рэалізацыі і, значыць, ён няправільны і быў апрабаваны ў першую чаргу.
Я напісаў дастаткова просты скрыпт на python, які адпраўляў фіктыўныя метрыкі для кожнага дня за мінулыя 4 гады і запускаўся кожную гадзіну кронам.
Бо ўся праца ClickHouse DBMS пабудавана на тым, што гэтая сістэма рана ці позна зробіць усю фонавую працу, але невядома калі, то дачакацца моманту, калі старыя велізарныя кавалкі зробяць ласку пачаць мерж з новымі маленькімі, мне не атрымалася. Стала зразумела, што трэба шукаць спосаб аўтаматызаваць прымусовыя аптымізацыі.
Інфармацыя ў сістэмных табліцах ClickHouse
Зірнем на структуру табліцы
- імя БД (
database
); - імя табліцы (
table
); - імя і ВД партыцыі (
partition
&partition_id
); - калі кавалак быў створаны (
modification_time
); - мінімальная і максімальная дата ў кавалку (партыцыянаванне ідзе па днях) (
min_date
&max_date
);
Таксама ёсць табліца
- імя БД (
Tables.database
); - імя табліцы (
Tables.table
); - узрост метрыкі, калі павінна быць прыменена наступная агрэгацыя (
age
);
Такім чынам:
- У нас ёсць табліца кавалкаў і табліца правілаў агрэгацыі.
- Аб'ядноўваем іх скрыжаванне і атрымліваем усе табліцы GraphiteMergeTree.
- Шукаем усе партыцыі, у якіх:
- больш за адзін кавалка
- ці надышоў момант прымяніць наступнае правіла агрэгацыі, і
modification_time
старэйшыя за гэты момант.
Рэалізацыя
Дадзены запыт
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
вяртае кожную з партыцый табліц * GraphiteMergeTree, мерж якіх павінен прывесці да вызвалення дыскавай прасторы. Засталася толькі справа за малым: прайсціся па іх усім з запытам OPTIMIZE ... FINAL
. У фінальнай рэалізацыі таксама ўлічаны той момант, што партыцыі з актыўным запісам чапаць няма неабходнасці.
Менавіта гэта і робіць праект
Калі запусціць праграму на серверы з ClickHouse, то яна проста пачне працаваць у рэжыме дэмана. Раз у гадзіну будзе выконвацца запыт, правяраючы, ці не з'явіліся новыя партыцыі старэйшыя за тры суткі, якія можна аптымізаваць.
У бліжэйшых планах – падаць, прынамсі, deb пакеты, а па магчымасці – яшчэ і rpm.
замест заключэння
За мінулыя 9 з лішнім месяцаў я ўсярэдзіне сваёй кампаніі
На распрацоўку запыту было патрачана некалькі літраў піва і адміна-дзён сумесна з
Крыніца: habr.com