ADG Оставить заявку
Блог Данные и аналитика 5 мин чтения

ClickHouse 20.4 в production: Materialized Views для предагрегации и реальное ускорение аналитики

Обновляем production-кластер клиента до ClickHouse 20.4: тестируем улучшенные Materialized Views для предагрегации метрик, Dictionary источники, замеряем результат.

Контекст момента

Выпуск ClickHouse 20.4 (апрель 2020): улучшенные Materialized Views, расширенные Dictionary источники, оконные функции в активной разработке

В мае мы строили дашборды DataLens поверх ClickHouse и в процессе делали materialized views для предагрегации - в том посте это упоминалось вскользь. Сейчас пришла пора разобраться подробнее: один из клиентов попросил провести обновление production-кластера до ClickHouse 20.4 и заодно посмотреть, что там изменилось в части Materialized Views. Смотрели, обновляли, замеряли.

Контекст: клиент и его данные

Клиент - ритейл, несколько каналов продаж, онлайн и офлайн. ClickHouse стоит на двух серверах в репликации, туда прилетают события из нескольких источников: транзакции, клики, события склада. Основная проблема была прозаичная: аналитические запросы с агрегациями по большим диапазонам дат работали секунд по 10, иногда больше. Аналитики жаловались - и справедливо. Дашборд, который грузится дольше 2-3 секунд, практически не используется.

Версия была 20.1, в которой MV уже работали, но с некоторыми ограничениями. В 20.4 кое-что поменяли.

Что поменялось в Materialized Views в 20.4

Основное, что нас интересовало в релизе 20.4 - это изменения в работе Materialized Views. Если коротко, два момента оказались практически значимыми.

Поддержка JOIN в запросе MV. До 20.4 запрос, лежащий в основе материализованного представления, мог обращаться только к одной таблице - той, на которую навешен триггер. Теперь можно делать JOIN с другими таблицами прямо в определении MV. Для нас это было важно: часть метрик требовала обогащения данными из справочников прямо в момент записи, и раньше это делалось отдельным ETL-шагом.

Более предсказуемое поведение при ошибках в MV. В старых версиях ошибка в материализованном представлении могла, в зависимости от настроек, либо молча проглатываться, либо блокировать INSERT в основную таблицу. В 20.4 поведение стало чище: materialized_views_ignore_errors работает более предсказуемо, и в логах видно что именно пошло не так. Для production это важнее, чем звучит: дебажить "вставка прошла, но данные в MV не попали" - то ещё удовольствие.

Как строили предагрегацию

Структура у нас стандартная для таких задач. Есть сырая таблица с событиями на движке MergeTree, туда льются данные. Поверх неё - materialized view, которое при каждом INSERT считает агрегаты и пишет результат в отдельную таблицу на AggregatingMergeTree.

Упрощённо схема выглядит так:

-- Целевая таблица с агрегатами
CREATE TABLE sales_daily_agg (
    date Date,
    region_id UInt32,
    category_id UInt32,
    revenue_state AggregateFunction(sum, Decimal(18,2)),
    orders_state AggregateFunction(count, UInt64)
) ENGINE = AggregatingMergeTree()
ORDER BY (date, region_id, category_id);

-- Материализованное представление
CREATE MATERIALIZED VIEW sales_daily_mv TO sales_daily_agg AS
SELECT
    toDate(event_time) AS date,
    region_id,
    category_id,
    sumState(amount) AS revenue_state,
    countState() AS orders_state
FROM sales_events
GROUP BY date, region_id, category_id;

Читать из такой таблицы нужно через sumMerge / countMerge - ClickHouse сам доделывает частичные агрегаты при SELECT. Выглядит немного непривычно, зато работает быстро.

Ключевой момент - AggregatingMergeTree хранит не финальные значения, а промежуточные состояния агрегатных функций. Это позволяет корректно объединять данные при слиянии кусков, не теряя точность. Для sum это кажется излишним, но для uniq (уникальные пользователи) - принципиально: нельзя просто складывать количества уникальных из разных кусков.

Обновление кластера

Обновление делали поочерёдно по нодам, не останавливая кластер целиком. ClickHouse поддерживает репликацию между разными версиями в процессе роллинг-апдейта - это задокументировано и работает.

Несколько наблюдений по процессу:

Резервная копия перед обновлением - обязательно. Не потому что мы ожидали проблем, а потому что production. Сделали clickhouse-backup create перед стартом.

Порядок обновления реплик имеет значение. Обновляли сначала реплику, которая не является лидером по репликации для основных таблиц, убеждались что всё работает - потом вторую. Суммарно на кластер из двух нод ушло около двух часов с учётом проверок.

Совместимость MV между версиями. Старые Materialized Views заработали без каких-либо изменений после обновления. Новые фичи (JOIN в MV) - это опциональные возможности, их нужно явно использовать при создании новых представлений.

Что получили на выходе

После обновления и создания предагрегированных таблиц переключили дашборды читать из них вместо raw-данных.

Запросы, которые раньше занимали 8-12 секунд - типа «продажи по регионам за квартал с разбивкой по категориям» - начали отвечать за 200-400 миллисекунд. Разница ощутимая. Аналитики это заметили сами, без того чтобы мы им рассказывали - просто перестали жаловаться.

Нагрузка на кластер при чтении тоже упала: вместо полного скана нескольких сотен миллионов строк запросы теперь читают предагрегированные данные, объём которых на несколько порядков меньше.

Обратная сторона: предагрегация - это компромисс. Запросы, которые не укладываются в заданные измерения агрегата, всё равно идут в сырые данные. Аналитики иногда придумывают срезы, которых мы не предусмотрели - тогда 10 секунд возвращаются. С этим ничего не поделать, кроме как добавлять новые MV под новые паттерны запросов - что мы и планируем делать по мере накопления понимания о том, что реально используется.

Dictionary источники и немного про оконные функции

В 20.1 добавили поддержку Redis как источника для Dictionary, а в 20.4 расширили возможности HTTP-источников. Мы потестировали HTTP Dictionary для обогащения данных из внутреннего справочника клиента, который живёт на отдельном сервисе. Работает, но с оговоркой: кеширование словаря нужно настраивать аккуратно, иначе либо данные устаревают, либо сервис-источник получает избыточную нагрузку при рефреше.

Оконные функции - пока в активной разработке, в продуктивный код не ставили. Следим за развитием.

Где сейчас

Кластер работает на 20.4, предагрегация настроена для основных паттернов запросов, дашборды через dwh-bi отвечают быстро. Следующее, что хотим сделать - настроить мониторинг покрытия: какой процент реальных запросов попадает в MV, а какой идёт в сырые данные. Это позволит понять, где ещё стоит добавить предагрегацию, не гадая.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.