ClickHouse 25 LTS: переходим на нативный JSON и разбираемся с tiered storage на объектном хранилище
Обновили production-кластер до ClickHouse 25 LTS: делимся опытом перехода на нативный JSON-тип, замерами скорости и изменениями в tiered storage на объектном хранилище.
ClickHouse 25 LTS - выход стабильного релиза с нативным JSON-типом и улучшенным tiered storage на объектных хранилищах
ClickHouse 25 LTS вышел, и мы достаточно быстро решили обновить production-кластер - не из желания быть на острие, а потому что два изменения в релизе касались нас напрямую: нативный JSON-тип и доработки tiered storage для объектных хранилищ. Оба пункта давно висели в нашем списке «хотим, но ждём стабильности».
Почему именно сейчас
Нативный JSON в ClickHouse существовал в экспериментальном виде несколько версий. Мы наблюдали за ним со стороны: feature-флаг, нестабильный DDL, периодические жалобы в issues на неожиданное поведение при сложных запросах. В LTS его перенесли в статус стабильного - это тот момент, когда мы переключаемся с «интересно» на «пробуем на production».
Tiered storage - наша давняя головная боль. Кластер у одного из заказчиков хранит аналитику событий за несколько лет, горячие данные живут на NVMe, холодные давно хотели переложить в S3-совместимое объектное хранилище. Прошлые версии tiered storage работали, но с рядом ограничений по поведению при merge и движению данных между уровнями, которые порождали странные ситуации в мониторинге.
Нативный JSON: что изменилось и зачем это нам
До 25 LTS мы хранили полуструктурированные данные стандартным способом - либо String с ручным JSONExtract*, либо Map(String, String) для плоских структур. Оба варианта работают, но с оговорками: JSONExtract тяжёлый при массовых запросах, Map не справляется с вложенностью.
Нативный тип JSON в 25 LTS хранит данные в колоночном виде внутри: каждый путь в JSON становится отдельной подколонкой. Это принципиально другой подход - не строка с парсингом на лету, а реальная колоночная структура, которая раскрывается при чтении.
Мы взяли тестовую таблицу с событиями, где payload - произвольный JSON от разных источников: примерно 30-40 регулярных полей и хвост редких. Сравнили три варианта на одном наборе данных:
String+JSONExtractString- наш текущий production-вариант.JSON-тип в 25 LTS - новый нативный.- Набор
Nullable(String)-колонок для часто используемых полей (то, что мы иногда делаем вручную для горячих путей).
На запросах, которые читают 3-5 конкретных путей из JSON по миллионам строк, нативный JSON выигрывает у String+JSONExtract заметно - разница в разы, не в процентах. Это ожидаемо: ClickHouse не парсит всю строку, а читает только нужные подколонки. На запросах, которые читают весь payload целиком (для дебага или экспорта), разница значительно меньше.
Интересный момент: схема в нативном JSON фиксируется не при создании таблицы, а при первом появлении пути в данных. Это удобно для источников с нестабильной структурой, но требует аккуратности - если один источник вдруг начнёт слать user_id как число, а другой как строку, ClickHouse попробует это разрешить, и результат может удивить. Мы добавили мониторинг на расхождение типов в system.columns через несколько дней после запуска - лучше поймать это до того, как оно проявится в запросе.
Tiered storage: что починили
Tiered storage в ClickHouse - это политики движения данных между дисками (disk policy). У нас конфигурация: hot (NVMe) -> warm (S3-совместимое хранилище). Движение происходит по TTL или по правилам заполненности диска.
До 25 LTS у нас периодически возникала ситуация: часть партов зависала в состоянии перемещения между уровнями, мерж между партами на разных уровнях вёл себя непредсказуемо, иногда оставляя временные куски на горячем диске дольше, чем должно. Это не разрушительно, но в мониторинге выглядело некрасиво и иногда давало ложные срабатывания по метрикам заполненности.
В 25 LTS команда Altinity и контрибьюторы довольно серьёзно поработали с механикой merge через уровни и с надёжностью метаданных на S3. После обновления поведение стало ощутимо чище: парты движутся предсказуемо, мерж не оставляет мусора на горячем уровне, метрики system.moves перестали показывать зависшие операции.
Конкретно нас ещё порадовала переработанная метрика MergedUncompressedBytes в разбивке по уровням - раньше для диагностики приходилось собирать её вручную из нескольких системных таблиц.
Как обновлялись
Обновление делали по одной реплике за раз - стандартная практика для кластера с репликацией. ClickHouse хорошо переносит rolling upgrade: пока одна реплика рестартует, остальные продолжают отвечать на запросы.
Единственный момент, который стоит учитывать при миграции на нативный JSON: существующие колонки типа String с JSON-содержимым сами по себе не мигрируют. Мы создали новые колонки с типом JSON, залили данные через INSERT ... SELECT с парсингом, поменяли источники данных. Это плановая работа, но её нужно заложить в scope заранее - «просто поменять тип ALTER-ом» не работает.
Где сейчас
Нативный JSON уже в production для двух таблиц с самой нестабильной схемой payload. Остальные пока остаются на String - не потому что плохо, а потому что миграция требует остановки источников на перенастройку, и это нужно координировать с командой заказчика. Tiered storage работает заметно спокойнее, мониторинг перестал шуметь.
Для DWH и аналитических задач с полуструктурированными данными нативный JSON в 25 LTS - реальное улучшение, а не маркетинг. Если у вас в ClickHouse живут данные с вариативной схемой и вы до сих пор разбираете их через JSONExtract, 25 LTS - хороший момент проверить, стоит ли переходить. Мы помогаем с аудитом и проектированием таких кластеров в рамках DWH/BI-проектов.