Back to changelog
Improvement
2 minute read

ClickHouse: Merged ALTER TABLE Statements

Atlas now merges multiple ALTER TABLE operations on the same table into a single statement, improving atomicity and removing redundant SETTINGS alter_sync in non-cluster mode.

When you change multiple columns, indexes, or table attributes in a single schema apply, Atlas previously generated a separate ALTER TABLE statement for each one. If one of them failed mid-way, the table could end up in a partially-migrated state that required manual cleanup.

Starting with this release, Atlas merges all compatible operations into a single ALTER TABLE statement. ClickHouse executes all clauses as one mutation: either everything applies or nothing does.

Before and After: Mixed Operations

Suppose you add a column, change a column type, and recreate an index in the same schema apply. Previously this produced four round-trips to ClickHouse:

-- 4 separate statements (before)
ALTER TABLE `orders` ADD COLUMN `discount` Decimal(5, 2) DEFAULT 0.00;
ALTER TABLE `orders` MODIFY COLUMN `total` Decimal(12, 2) DEFAULT 0.00;
ALTER TABLE `orders` DROP INDEX `idx_status`;
ALTER TABLE `orders` ADD INDEX `idx_status` (`status`) TYPE set(100);

Atlas now produces a single statement that applies all changes at once:

-- 1 merged statement (after)
ALTER TABLE `orders`
ADD COLUMN `discount` Decimal(5, 2) DEFAULT 0.00,
MODIFY COLUMN `total` Decimal(12, 2) DEFAULT 0.00,
DROP INDEX `idx_status`,
ADD INDEX `idx_status` (`status`) TYPE set(100);

Before and After: Column Attributes

Adding a compression codec, setting a TTL, updating a column comment, and changing a default on the same table are also combined into one statement:

-- 4 separate statements (before)
ALTER TABLE `users` MODIFY COLUMN `email` String CODEC(ZSTD(3));
ALTER TABLE `users` MODIFY COLUMN `email` String TTL created_at + toIntervalDay(365);
ALTER TABLE `users` COMMENT COLUMN `email` 'user primary email, PII';
ALTER TABLE `users` MODIFY COLUMN `updated_at` DateTime64(3) DEFAULT now64();
-- 1 merged statement (after)
ALTER TABLE `users`
MODIFY COLUMN `email` String CODEC(ZSTD(3)),
MODIFY COLUMN `email` String TTL created_at + toIntervalDay(365),
COMMENT COLUMN `email` 'user primary email, PII',
MODIFY COLUMN `updated_at` DateTime64(3) DEFAULT now64();

Cluster Mode

In cluster mode, Atlas appends SETTINGS alter_sync = 2 once at the end of the merged statement so the coordinator waits for all replicas before returning.

In non-cluster (single-node) mode, the previously emitted SETTINGS alter_sync = 1 is dropped entirely because 1 is already the ClickHouse default. This makes generated migrations shorter and easier to read.

-- Cluster mode: alter_sync is appended once at the end
ALTER TABLE `events` ON CLUSTER `{cluster}`
ADD COLUMN `region` LowCardinality(String) DEFAULT 'us-east-1',
DROP INDEX `idx_session`,
ADD INDEX `idx_session` (`session_id`) TYPE bloom_filter(0.01)
SETTINGS alter_sync = 2;

What is Not Merged

A few operations cannot be combined with others and will still get their own statement:

  • MODIFY SETTING and RESET SETTING (ClickHouse requires these to be standalone)
  • Column recreations where the column must be dropped and re-added because the type change cannot be done in place

After adding an index or projection, Atlas still emits a separate MATERIALIZE INDEX / MATERIALIZE PROJECTION statement to back-fill existing data. This cannot be part of the same ALTER TABLE because ClickHouse requires the index/projection to exist before materializing it.

improvementclickhouseatomicityalter-table