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

ClickHouse 21.3 LTS: оконные функции наконец в production, мигрируем аналитику

ClickHouse 21.3 - первая LTS-версия с оконными функциями. Переводим клиентский аналитический пайплайн с агрегирующими MV на новую версию - что получилось.

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

ClickHouse 21.3 LTS: улучшенные Materialized Views, оконные функции, интеграция с S3

В конце марта вышел ClickHouse 21.3 - первый релиз с долгосрочной поддержкой (LTS). Главные новости: оконные функции вышли из экспериментального статуса, Materialized Views стали гибче, плюс появилась нативная интеграция с S3 через движок таблиц. Для нас это был повод наконец перевести один затянувшийся проект с нестабильного набора 20.x-релизов на что-то, за чем следят патчами.

Рассказываем про миграцию клиентского аналитического DWH и что из новинок реально пригодилось.

Контекст: пайплайн на агрегирующих MV и почему он болел

У клиента - ритейл, онлайн-заказы - в ClickHouse крутится пайплайн обработки событий: клики, сессии, конверсии. Под каждый тип агрегации - отдельный AggregatingMergeTree с набором Materialized Views, которые считают STATE-функции инкрементально по мере вставки.

Схема рабочая, но с двумя хроническими болячками.

Первая: скользящее среднее и running total считались отдельной джобой на Python, которая ночью поднимала данные из ClickHouse, считала, и писала обратно в другую таблицу. Потому что оконных функций в ClickHouse нормально не было - был runningAccumulate, который хитрый, капризный и не то чтобы легко читается коллегами.

Вторая: при рестарте и восстановлении данных Materialized Views нужно было пересчитывать вручную через INSERT INTO ... SELECT, и этот процесс был не атомарным - в переходное время цифры в BI расходились. Клиент на это смотрел кисло, и правильно смотрел.

Оконные функции: с осторожным оптимизмом

В 21.3 OVER() перестал быть экспериментальным. ROW_NUMBER(), RANK(), LAG/LEAD, SUM() OVER (ORDER BY ...) - всё это теперь работает в запросах без SET allow_experimental_window_functions = 1.

Первым делом переписали Python-джобу на SQL прямо в ClickHouse. Running total по дням:

SELECT
    date,
    revenue,
    SUM(revenue) OVER (ORDER BY date ROWS UNBOUNDED PRECEDING) AS revenue_cumulative
FROM daily_revenue
ORDER BY date;

Скользящее среднее за 7 дней:

SELECT
    date,
    revenue,
    AVG(revenue) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS revenue_ma7
FROM daily_revenue
ORDER BY date;

Это работает. Python-джоба ушла в архив. Минус один компонент в пайплайне, минус один кронджоб, минус одна точка отказа.

Оговорка, которую стоит держать в голове: оконные функции в ClickHouse работают не так, как в PostgreSQL. ClickHouse сначала применяет WHERE и GROUP BY, потом поверх - окно. То есть нельзя написать что-то вроде WHERE rank = 1 в том же запросе где вычисляется RANK() - нужен подзапрос. Для людей с PostgreSQL-фоном это сюрприз в первый раз. Документация на это указывает, но не особо акцентирует.

По производительности оконных функций на больших объёмах данных у нас опыта накопилось немного - нагрузка клиента умеренная. Отдельные запросы с окнами на десятках миллионов строк мы гоняли через EXPLAIN и смотрели на LIMIT - ClickHouse умеет оптимизировать оконные запросы с LIMIT, не вытягивая весь результат. Пока держит.

Materialized Views: что стало лучше

Две вещи в 21.3 по Materialized Views заметны на практике.

Первое - POPULATE плюс IF NOT EXISTS. Раньше при пересоздании MV нужно было следить за порядком: создать MV, потом вручную залить исторические данные. В 21.3 поведение при CREATE MATERIALIZED VIEW ... POPULATE стало чуть предсказуемее с точки зрения гарантий - MV при POPULATE блокирует входящий поток и заливает данные атомарнее, чем раньше. Не идеально, но расхождения в BI во время пересчёта стали заметно реже.

Второе - FINAL в источнике MV. Теперь можно использовать SELECT ... FINAL в теле Materialized View, что позволяет читать из ReplacingMergeTree или CollapsingMergeTree уже схлопнутые данные. До этого MV видело «сырые» данные до мержа партов, что давало дублирование в агрегатах на горячих данных. Это был один из хронических источников расхождений.

Для клиента переписали несколько MV с явным FINAL - на горячих данных (последние сутки) расхождения в агрегатах с уровнем в промежуточных партах упали до нуля.

Интеграция с S3

У клиента хранение исходных событий до загрузки в ClickHouse идёт через S3-совместимое хранилище (Яндекс Object Storage). В 21.3 движок S3 в табличных функциях и CREATE TABLE ... ENGINE = S3(...) получил поддержку Parquet и стабильную работу с wildcards в путях.

Раньше читали S3 через Python + boto3 + clickhouse-driver. Теперь можно делать прямо из SQL:

SELECT *
FROM s3('https://storage.yandexcloud.net/bucket/events/*.parquet', 'AccessKey', 'SecretKey', 'Parquet')
WHERE event_date = '2021-04-28'
LIMIT 100;

Для ad-hoc анализа сырых данных или диагностики загрузчика это удобно. В регулярный пайплайн пока не встраивали - хочется сначала посмотреть как ведёт себя на больших объёмах. Но возможность приятная.

Про саму миграцию с 20.x на 21.3

Миграция прошла без особых драм. Совместимость данных между минорными версиями ClickHouse держит хорошо - форматы партов не меняются между 20.x и 21.3.

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

Единственная зацепка: один агрегирующий запрос с AggregateFunction(uniqExact, ...) вернул другой результат после обновления одной реплики. Оказалось, что в новой версии изменилось поведение мержа uniqExact-стейтов при определённых условиях. После полного обновления всех реплик результаты выровнялись. Прописали в memory/lessons.md - при смешанном кластере агрегирующие функции с STATE могут временно давать несогласованные результаты.

Где сейчас

Клиент на 21.3 уже две недели. Python-джоба на расчёт оконных метрик убрана, MV переписаны с FINAL, расхождения в BI стали реже. Открытый вопрос - S3-интеграция в основной пайплайн: смотрим на неё как на замену Python-загрузчика, но сначала нужно понять поведение на пиковых нагрузках.

LTS-статус 21.3 для нас значит одно: можно спокойно рекомендовать её клиентам как базу для production без оглядки на то, что через месяц выйдет 21.4 и что-то сломает. Для тех, кто сидит на 20.x и жалуется на нестабильность нечётных релизов - пора смотреть на переезд.

Контакт

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

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