Atomic DDL: EXCHANGE TABLES и zero-downtime миграции
Обновление схемы таблицы в production без даунтайма — одна из ключевых задач операционного управления ClickHouse. EXCHANGE TABLES обеспечивает атомарный swap двух таблиц: в один момент читатели видят старую версию, в следующий — новую, без переходного состояния. Это делает возможными zero-downtime миграции даже на таблицах с активными INSERT и SELECT.
EXCHANGE TABLES: атомарный swap
EXCHANGE TABLES a AND b — DDL-команда, которая атомарно меняет местами две таблицы. Читатели никогда не видят промежуточное состояние.
-- Создать новую версию таблицы с обновлённой схемой
CREATE TABLE events_v2 AS events; -- Скопировать структуру
-- Наполнить новую таблицу данными
INSERT INTO events_v2 SELECT * FROM events;
-- Применить изменения схемы к новой таблице
ALTER TABLE events_v2 ADD COLUMN region LowCardinality(String) DEFAULT '';
-- Атомарный swap: в один момент events начинает указывать на новую версию
EXCHANGE TABLES events AND events_v2;
-- Теперь:
-- events — таблица с новой схемой (обновлённые данные)
-- events_v2 — старая таблица (для проверки и rollback)
DROP TABLE events_v2; -- Очистить старую версию
Atomic database engine — default с ClickHouse 20.x. EXCHANGE TABLES работает без дополнительной настройки на всех актуальных версиях ClickHouse. Проверить тип engine базы данных: SELECT engine FROM system.databases WHERE name = 'default'.
RENAME TABLE: не атомично для нескольких объектов
RENAME TABLE выполняет переименование мгновенно для одного объекта, но не является атомичным при переименовании нескольких таблиц в одном операторе.
-- Одиночное RENAME: безопасно (атомично для 1 объекта)
RENAME TABLE events_old TO events_backup;
-- RENAME нескольких объектов: НЕ атомично
-- Между первым и вторым переименованием клиент может получить ошибку
RENAME TABLE events TO events_old, events_new TO events; -- Опасно!
RENAME TABLE — не атомично для нескольких объектов. Если выполнять RENAME TABLE a TO b, c TO a для горячей замены таблицы, между двумя операциями существует временное состояние, когда ни один из вариантов имени не работает корректно. Для безопасного hot-swap используйте EXCHANGE TABLES.
Сравнение: EXCHANGE vs RENAME
| Критерий | EXCHANGE TABLES | RENAME TABLE |
|---|---|---|
| Атомарность | Полная (транзакционная) | Только для одного объекта |
| Несколько объектов | Не применимо (2 таблицы фиксированы) | Не атомично |
| Читатели во время операции | Видят только старую или только новую версию | Могут получить ошибку “table not found” |
| Требует Atomic engine | Да (default с 20.x) | Нет |
| Rollback | EXCHANGE TABLES events AND events_v2 снова | Ещё одно RENAME TABLE |
Zero-downtime migration pattern
Полный паттерн замены таблицы с новой схемой без остановки сервиса:
-- Шаг 1: Создать новую таблицу с обновлённой схемой
CREATE TABLE events_v2 (
event_time DateTime64(3) CODEC(Delta, ZSTD(1)),
user_id UInt64,
action LowCardinality(String),
region LowCardinality(String) DEFAULT '', -- Новая колонка
session_id String DEFAULT '' -- Ещё одна новая колонка
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events_v2', '{replica}')
PARTITION BY toYYYYMM(event_time)
ORDER BY (user_id, event_time);
-- Шаг 2: Перелить исторические данные (параллельно с production-трафиком)
INSERT INTO events_v2
SELECT
event_time,
user_id,
action,
'' AS region, -- default для новой колонки
'' AS session_id -- default для новой колонки
FROM events;
-- Шаг 3: Переключить production-трафик на новую таблицу
-- (применить новые ALTER к events_v2 если нужно)
ALTER TABLE events_v2 ADD COLUMN new_feature_flag UInt8 DEFAULT 0;
-- Шаг 4: Атомарный swap -- читатели не замечают переключения
EXCHANGE TABLES events AND events_v2;
-- Шаг 5: Очистить старую версию (теперь в events_v2 -- старые данные)
DROP TABLE events_v2;
Когда использовать EXCHANGE vs обычный ALTER
-- Предпочтительно ALTER когда:
-- - ADD/DROP COLUMN (metadata-only, instant)
-- - MODIFY COLUMN с совместимым типом
ALTER TABLE events ADD COLUMN extra_data String DEFAULT '';
-- Предпочтительно EXCHANGE TABLES когда:
-- - Полное переписывание схемы (ORDER BY меняется, типы несовместимы)
-- - Нужна гарантия rollback (старая таблица остаётся как events_v2)
-- - Данные нужно трансформировать при переносе (ETL + migrate)
-- - Смена движка таблицы (MergeTree -> ReplacingMergeTree)
EXCHANGE TABLES events AND events_v2;
Проверка завершения migration
-- Убедиться, что INSERT в events_v2 завершён
SELECT count() FROM events_v2;
-- Должно совпадать с events до swap
-- Проверить структуру после swap
SELECT name, type, default_expression
FROM system.columns
WHERE database = 'default' AND table = 'events'
ORDER BY position;
-- Проверить, что нет активных mutations на новой таблице
SELECT mutation_id, command, is_done
FROM system.mutations
WHERE database = 'default' AND table = 'events' AND is_done = 0;
Ключевые выводы
EXCHANGE TABLES a AND b— атомарный swap: Читатели никогда не видят промежуточное состояние. Требует Atomic database engine (default с ClickHouse 20.x).RENAME TABLEне атомично для нескольких объектов: При переименовании двух таблиц в одном операторе между операциями существует временное состояние с возможными ошибками “table not found”.- Zero-downtime migration pattern: CREATE TABLE new -> INSERT INTO SELECT -> ALTER new -> EXCHANGE -> DROP old. Читатели продолжают работать с production-трафиком на всех шагах.
- Rollback через повторный EXCHANGE: До DROP TABLE old всегда можно вернуться к предыдущей версии одной командой
EXCHANGE TABLES events AND events_v2.