Комментарии 2
Позеленил.
Еще бы продолжить после слов " Если по привычке оставить ON CLUSTER в Replicated-базе, поведение будет не тем, что вы ожидаете - ":
ClickHouse может попытаться выполнить команду дважды (один раз через механизм кластера, второй раз через механизм реплицируемой базы), что приведет к рассинхронизации метаданных, блокировкам, дублированию таблиц или другим сложным для отладки багам.
У нас скрипты по ландшафту через liquibase сейчас ездят.
Спасибо за текст. Редкий случай, когда про миграции ClickHouse пишут не в жанре «поставьте goose и не мучайтесь».
Раз спрашиваете про Replicated-базу на проде, отвечу и добавлю пять мест, где эта тема обычно и ломается.
Семантика отказа у ON CLUSTER. Запрос отдаёт ошибку по distributed_ddl_task_timeout, но задача из очереди никуда не исчезает и доезжает до реплик позже. Мигратор, который трактует таймаут как провал и не пишет строку в историю, на следующем прогоне пытается накатить уже применённый DDL. Вердикт надо ставить по system.distributed_ddl_queue для конкретного entry, а не по коду возврата клиента. Интересно, как это закрыто у вас.
Путь в ZooKeeper для таблицы истории. Про согласованность на шардах вы пишете, но ключевая деталь в макросах: если в пути есть {shard}, история реплицируется внутри шарда и расходится между шардами. Нужен общий путь без {shard}, тогда все ноды кластера становятся репликами одной таблицы истории. И движок лучше Replacing: при параллельном старте нескольких подов сервиса гонка на вставке даёт дубли строк, а не ошибку.
Мутации это не DDL. ALTER TABLE … UPDATE/DELETE возвращает управление сразу, работа идёт в фоне. Мигратор проставляет успех, пайплайн едет дальше, а мутация может висеть часами или упасть на мердже. mutations_sync=2 закрывает простой случай, но на больших таблицах упирается в таймаут клиента, так что нужен отдельный шаг ожидания по system.mutations с потолком по времени. Если в релизе DDL и мутации вперемешку, батч по общему apply_time перестаёт быть единицей отката даже в вашей ослабленной трактовке.
Про down в ClickHouse. По опыту честнее не поддерживать откат вовсе, а идти expand/contract: новая колонка, бэкофилл, переключение чтения, дроп старой, четыре отдельные миграции, каждая безопасна в обе стороны. Команда down создаёт ожидание, что откат существует, хотя он существует только для метаданных.
Iceberg. Здесь роль инструмента меняется по сути: историю схемы ведёт сам каталог, снапшоты уже версионированы, поэтому ценность смещается с версионирования на воспроизводимость DDL между окружениями и на эволюцию partition spec. Это место злее, чем выглядит: смена spec не переписывает старые данные, старые партиции остаются в прежней схеме разбиения, и планировщик работает с обеими одновременно. Если dry-run научится показывать результирующий spec и объём данных, оставшийся в старом, это будет сильнее любого наката DDL.
Если возьмётесь за отдельный текст про Iceberg, самый нужный кейс это миграция partition spec на живой таблице, а не CREATE TABLE.

Как я написал свой мигратор для ClickHouse — и почему он до сих пор жив