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 и жалуется на нестабильность нечётных релизов - пора смотреть на переезд.