Вітаю, 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