Schema-миграции: стоимость DDL-операций
Изменения схемы в ClickHouse имеют разную стоимость в зависимости от типа операции. ADD COLUMN выполняется мгновенно, но MODIFY COLUMN с изменением несовместимого типа запустит background mutation, которая может переписывать данные часами. Понимание стоимости каждой DDL-операции — ключ к безопасным миграциям в production без неожиданных простоев.
Стоимость DDL-операций
В ClickHouse части (parts) не переписываются немедленно при большинстве DDL-команд. Вместо этого изменения применяются к метаданным, а физическая запись происходит при следующем merge или через background mutation.
| Операция | Поведение | Стоимость |
|---|---|---|
ADD COLUMN | Metadata-only, мгновенно. Старые части синтезируют default-значение при чтении | Минимальная |
DROP COLUMN | Мгновенно. Части не переписываются до следующего merge | Низкая (merge всё равно произойдёт) |
MODIFY COLUMN (совместимый тип) | Мгновенно (например, Int32 -> Int64, String остаётся String) | Нет |
MODIFY COLUMN (несовместимый тип) | Запускает background mutation — переписывает все части | Высокая — может занять часы |
ADD COLUMN
-- Мгновенное добавление колонки -- metadata only
ALTER TABLE events ADD COLUMN session_id String DEFAULT '';
-- Старые части продолжают работать: session_id будет возвращать default-значение
-- без физической перезаписи данных
SELECT session_id FROM events LIMIT 5;
-- Вернёт: '' '' '' '' ''
Добавление колонки в ClickHouse не реписывает ни одной части. Существующие части при чтении синтезируют значение из DEFAULT или возвращают тип-по-умолчанию.
DROP COLUMN
-- Мгновенное удаление колонки -- части не переписываются немедленно
ALTER TABLE events DROP COLUMN old_metric;
-- Физическое удаление произойдёт при следующем merge старых частей
-- До тех пор diskspace не освобождается
DROP COLUMN мгновенно запрещает доступ к колонке, но физически данные остаются на диске до следующего merge. Для принудительного освобождения места используйте OPTIMIZE TABLE events FINAL (но только в dev/staging — в production это дорогая операция).
MODIFY COLUMN
-- Мгновенно: совместимый тип (расширение диапазона)
ALTER TABLE events MODIFY COLUMN user_id Int64; -- Int32 -> Int64, instant
-- Запускает mutation: несовместимый тип
ALTER TABLE events MODIFY COLUMN status_code Int32; -- String -> Int32: mutation!
MODIFY COLUMN с несовместимым типом (например, String -> Int32) запускает background mutation. Проверьте после ALTER:
SELECT count() FROM system.mutations WHERE is_done = 0 AND table = 'events';Пока мutation не завершена (is_done = 0), данные переписываются в фоне. На больших таблицах это может занять часы.
ON CLUSTER DDL: порядок имеет значение
В шардированном или реплицированном кластере DDL-команды с ON CLUSTER требуют строгого порядка: сначала меняется схема на всех узлах, затем деплоится новый код приложения.
-- Правильный порядок: сначала DDL на кластере
ALTER TABLE events ON CLUSTER 'mycluster' ADD COLUMN new_col String DEFAULT '';
-- Убедитесь, что команда выполнена на ВСЕХ узлах:
SELECT hostname(), count() FROM clusterAllReplicas('mycluster', system.columns)
WHERE table = 'events' AND name = 'new_col'
GROUP BY hostname();
-- Только после этого -- деплой нового кода, читающего new_col
Если задеплоить новый код ДО выполнения ALTER TABLE ON CLUSTER, часть узлов не будет знать о новой колонке. Это split-brain: одни реплики отвечают данными, другие возвращают ошибку “unknown column”. Всегда выполняйте DDL ON CLUSTER ПЕРЕД деплоем кода.
Проверка завершения DDL на всех узлах кластера:
-- Убедиться, что все реплики знают о новой колонке
SELECT hostname(), name, type
FROM clusterAllReplicas('mycluster', system.columns)
WHERE database = 'default' AND table = 'events' AND name = 'new_col'
ORDER BY hostname();
Replicated Database Engine: автоматическая репликация DDL
ReplicatedMergeTree таблицы используют ClickHouse Keeper для репликации данных. Но для репликации самих DDL-команд (CREATE TABLE, ALTER TABLE) требуется Replicated database engine.
-- Создание Replicated базы данных
CREATE DATABASE prod_db
ENGINE = Replicated('/clickhouse/databases/prod_db', '{shard}', '{replica}');
-- Теперь любой DDL внутри prod_db автоматически реплицируется:
-- не нужно добавлять ON CLUSTER к каждому ALTER TABLE
CREATE TABLE prod_db.events (
event_time DateTime,
user_id UInt64,
action LowCardinality(String)
) ENGINE = ReplicatedMergeTree()
ORDER BY (user_id, event_time);
-- Таблица создаётся на всех узлах кластера автоматически
Replicated database engine — рекомендованный подход для кластеров, где DDL должен применяться ко всем узлам без ручного ON CLUSTER. Подробнее об архитектуре репликации — в Модуле 09 урок 08.
-- Проверка состояния Replicated базы данных
SELECT database, shard_name, replica_name, is_recovery
FROM system.clusters
WHERE cluster LIKE '%prod%';
Мониторинг миграций
-- Все активные mutations (is_done = 0 -- ещё не завершены)
SELECT
database,
table,
mutation_id,
command,
is_done,
parts_to_do,
parts_to_do_names
FROM system.mutations
WHERE is_done = 0
ORDER BY create_time;
-- Активные background merges и mutations
SELECT
database,
table,
result_part_name,
reason,
progress
FROM system.merges
WHERE reason IN ('TTL_DELETE', 'MUTATE_PART', 'MERGE_PARTS')
ORDER BY start_time DESC;
Ключевые выводы
ADD COLUMN— metadata-only: Мгновенно, без перезаписи частей. Старые части синтезируют default-значение при чтении. Безопасно в production в любой момент.DROP COLUMN— мгновенно для доступа: Колонка недоступна сразу, но физическое удаление данных на диске происходит при следующем merge частей.MODIFY COLUMNс несовместимым типом запускает mutation: Проверяйтеsystem.mutations WHERE is_done = 0после любого MODIFY COLUMN — особенно при смене типа на несовместимый.- ON CLUSTER DDL ordering: DDL выполняется на всех узлах кластера ПЕРЕД деплоем нового кода. Обратный порядок вызывает split-brain состояние.
- Replicated database engine автоматически реплицирует DDL без
ON CLUSTER— рекомендован для production кластеров с частыми изменениями схемы.