Safety classification¶
Every change dbwarden detects is classified. Destructive operations require --force and emit warnings with reasons.
Classification levels¶
| Level | Color | Behavior |
|---|---|---|
INFO |
Green | Applied automatically. Safe metadata changes: ADD COLUMN, ADD INDEX, ADD PROJECTION, SETTING changes, TTL changes. |
WARN |
Yellow | Applied automatically but logged as a warning. Attention recommended: DROP COLUMN, DROP TABLE, DROP INDEX, mutations, partition DROP/REPLACE. |
CRITICAL |
Red | Skipped unless --force is passed. Requires explicit acknowledgement: engine changes, ORDER BY non-extension, PRIMARY KEY changes, MV TO target change, type incompatibility, LowCardinality/Nullable toggle. |
The --force flag¶
# Preview what would run
dbwarden make-migrations --plan --force -d analytics
# Apply with force
dbwarden migrate --force -d analytics
--force is all-or-nothing for CRITICAL items in the plan. There is no per-item override.
The recreate pipeline¶
When a CRITICAL change requires a full table rebuild, dbwarden executes:
DETACH TABLE source
CREATE TABLE source_new (...new definition...)
INSERT INTO source_new SELECT * FROM source
RENAME TABLE source TO source_old,
source_new TO source
ATTACH TABLE source_old
Steps:
1. DETACH: unmounts the table metadata from the database (data remains on disk)
2. CREATE new: creates a table with the new definition
3. INSERT INTO ... SELECT: copies all data from old to new (blocking if source is still receiving writes)
4. RENAME: atomically swaps source and source_old under an exclusive lock
5. ATTACH: remounts the old table as a backup under its new name
After the pipeline, source has the new definition and source_old holds the original data. Verification steps:
SELECT count(*) FROM source
SELECT engine, create_table_query FROM system.tables WHERE name = 'source'
If something went wrong, swap back:
Rollback of a recreate¶
The rollback is the reverse pipeline:
DETACH TABLE source_old
CREATE TABLE source (...original definition...)
INSERT INTO source SELECT * FROM source_old
RENAME TABLE source TO source_old2,
source_old TO source
ATTACH TABLE source_old2
This restores the original table and keeps the failed new table as source_old2.
When to use --force¶
- Staging/testing: always use
--forceto verify the pipeline works - Production small tables (< 40 GB):
--forcewith a maintenance window - Production large tables (> 40 GB): avoid
--force. Instead, manually plan a zero-downtime migration usingclickhouse-copieror double-write during backfill
Additional model examples¶
Model that triggers CRITICAL classification¶
# Current table for reference:
class OldEvents(Base):
__tablename__ = "events"
id: Mapped[int] = mapped_column(primary_key=True)
event_date: Mapped[date] = mapped_column()
class Meta(CHTableMeta):
ch = ch_table(engine=merge_tree(), order_by=["event_date", "id"])
# Change: remove a column from ORDER BY (non-extension)
class UpdatedEvents(Base):
__tablename__ = "events"
id: Mapped[int] = mapped_column(primary_key=True)
event_date: Mapped[date] = mapped_column()
class Meta(CHTableMeta):
ch = ch_table(engine=merge_tree(), order_by=["event_date"]) # 'id' removed
Plan output:
CRITICAL: Changing ORDER BY from (event_date, id) to (event_date) requires --force
Apply with: dbwarden migrate --force -d analytics
Safe change that passes without --force¶
# Add a column and extend ORDER BY
class SafeEvents(Base):
__tablename__ = "events"
id: Mapped[int] = mapped_column(primary_key=True)
event_date: Mapped[date] = mapped_column()
status: Mapped[str] = mapped_column()
class Meta(CHTableMeta):
ch = ch_table(engine=merge_tree(), order_by=["event_date", "id", "status"])
Plan output:
ALTER TABLE events ADD COLUMN status String (INFO)
ALTER TABLE events MODIFY ORDER BY (event_date, id, status) (INFO)
Recreate pipeline example with verification¶
# 1. Preview the recreate
$ dbwarden make-migrations --plan --force -d analytics
# 2. Apply
$ dbwarden migrate --force -d analytics
# 3. Verify the new table
$ clickhouse-client -q "SELECT count(*) FROM events"
$ clickhouse-client -q "SELECT engine, create_table_query FROM system.tables WHERE name = 'events'"
# 4. If rollback needed:
$ clickhouse-client -q "RENAME TABLE events TO events_broken, events_old TO events"
ClickHouse-native safety features¶
dbwarden also respects ClickHouse's server-side allow_ddl and readonly settings. If the server refuses a DDL statement, dbwarden logs the error and continues with the remaining plan items.