ClickHouse + Graphite: як значно зменшити споживане місце на дисках

ClickHouse + Graphite: як значно зменшити споживане місце на дисках

Вітаю, habr.

Якщо хтось експлуатує систему graphite-web та зіткнувся з проблемою продуктивності сховища шепотіти (IO, споживаний дисковий простір), то шанс того, що був кинутий погляд на ClickHouse як заміну, повинен прагнути до одиниці. Дане твердження має на увазі те, що в якості приймаючого метрики демона вже використовується стороння реалізація, наприклад carbonwriter або go-carbon.

ClickHouse добре вирішує описані проблеми. Наприклад, після переливання 2TiB даних з whisper, вони вмістилися в 300GiB. Докладно на порівнянні я зупинятись не буду, статей на цю тему вистачає. До того ж, до недавнього часу з нашим ClickHouse сховищем було не ідеально.

Проблеми із споживаним місцем

На перший погляд, все має працювати добре. Дотримуючись документації, створюємо конфіг для схеми зберігання метрик (далі retention), потім створюємо таблицю згідно з рекомендацією обраного бекенда для graphite-web: carbon-clickhouse+graphite-clickhouse або graphouse, в залежності від того, який стек використовується. І… вмикається бомба уповільненої дії.

Для того, щоб зрозуміти, яка, треба знати, як працюють вставки та подальший життєвий шлях даних у таблицях двигунів сімейства *MergeTree ClickHouse (діаграми взяті з презентації Олексія Зателепіна):

  • вставляється блок даних. У нашому випадку це прилетіли до метрики.
    ClickHouse + Graphite: як значно зменшити споживане місце на дисках
  • Кожен такий блок перед записом на диск сортується за ключом ORDER BY, зазначеному під час створення таблиці.
  • Після сортування, кусок (part) даних записується на диск.
    ClickHouse + Graphite: як значно зменшити споживане місце на дисках
  • Сервер стежить у фоні, щоб таких шматків було небагато, і запускає фонові слияния (merge, Далі мержі).
    ClickHouse + Graphite: як значно зменшити споживане місце на дисках
    ClickHouse + Graphite: як значно зменшити споживане місце на дисках
  • Сервер перестає запускати мержі самостійно, як тільки дані перестають активно надходити в партицию (partition), але можна запустити процес вручну командою OPTIMIZE.
  • Якщо у партиції залишився лише один шматок, то запустити мерж звичайною командою не вийде, необхідно використовувати OPTIMIZE ... FINAL

Отже, надходять перші метрики. І вони займають певний простір. Наступні події можуть дещо змінюватись в залежності від багатьох факторів:

  • Ключ партиціонування може бути дуже маленьким (день), і дуже великим (кілька місяців).
  • Конфіг retention може вміщати кілька значних порогів агрегації даних усередині активної партиції (куди йде запис метрик), а може й ні.
  • Якщо даних дуже багато, то ранні шматки, які з-за фонових мереж можуть вже бути величезними (при виборі неоптимального ключа партиціонування), не мереться самі зі свіжими маленькими шматками.

І закінчується завжди все одно. Місце, яке займає метрики в ClickHouse тільки зростає, якщо:

  • не застосовувати OPTIMIZE ... FINAL вручну або
  • не вставляти дані у всі партиції на постійній основі, щоб рано чи пізно запустити фоновий мерж

Другий спосіб здається найбільш простим у реалізації і, отже, він неправильний і був випробуваний насамперед.
Я написав досить простий скрипт на python, який відправляв фіктивні метрики для кожного дня за минулі 4 роки і запускався щогодини кроном.
Так як вся робота ClickHouse DBMS побудована на тому, що ця система рано чи пізно зробить всю фонову роботу, але невідомо коли, дочекатися моменту, коли старі величезні шматки дозволять почати мерж з новими маленькими, мені не вдалося. Стало ясно, що треба шукати спосіб автоматизувати примусову оптимізацію.

ClickHouse + Graphite: як значно зменшити споживане місце на дисках

Інформація у системних таблицях ClickHouse

Погляньмо на структуру таблиці system.parts. Це вичерпна інформація про кожен шматок всіх таблиць на сервері ClickHouse. Містить, у тому числі, такі стовпці:

  • ім'я БД (database);
  • ім'я таблиці (table);
  • ім'я та ВД партиції (partition & partition_id);
  • коли шматок був створений (modification_time);
  • мінімальна та максимальна дата в шматку (партиціонування йде по днях) (min_date & max_date);

Також є таблиця system.graphite_retentions, з наступними цікавими полями:

  • ім'я БД (Tables.database);
  • ім'я таблиці (Tables.table);
  • вік метрики, коли має бути застосована наступна агрегація (age);

Отже:

  1. У нас є таблиця шматків та таблиця правил агрегації.
  2. Об'єднуємо їх перетин і отримуємо всі таблиці GraphiteMergeTree.
  3. Шукаємо всі партиції, в яких:
    • більше одного шматка
    • або настав момент застосувати таке правило агрегації, та 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. У фінальній реалізації також враховано той момент, що партиції з активним записом чіпати не потрібно.

Саме це і робить проект graphite-ch-optimizer. Колишні колеги з Яндекс.Маркет випробували його в проді, результат роботи можна побачити нижче.

ClickHouse + Graphite: як значно зменшити споживане місце на дисках

Якщо запустити програму на сервері з ClickHouse, вона просто почне працювати в режимі демона. Раз на годину виконуватиметься запит, перевіряючи, чи не з'явилися нові партиції старші за три доби, які можна оптимізувати.

У найближчих планах — надати, принаймні, пакети deb, а по можливості — ще й rpm.

Замість висновку

За минулі 9 з лишком місяців я всередині своєї компанії інноігри провів багато часу, варячись на стику ClickHouse та graphite-web. Це був хороший досвід, результатом якого став можливий швидкий перехід з whisper на ClickHouse як сховище метрик. Сподіваюся, що ця стаття — щось подібне до початку циклу про те, які поліпшення були внесені нами в різні частини цього стеку, і що буде зроблено в майбутньому.

На розробку запиту було витрачено кілька літрів пива та адміноднів спільно з v0devilза що я хочу висловити йому свою подяку. А також за рецензування цієї статті.

Сторінка проекту на github

Джерело: habr.com

Додати коментар або відгук