Liquibase vs SDT: schema management for Snowflake
If you're evaluating Liquibase for Snowflake and wondering whether a migration-based tool is the right shape for a warehouse, this is the comparison I'd have wanted. It's written by someone who builds a competing tool, so read it with that in mind — but I've tried to be specific about where Liquibase is genuinely the better choice, because if I get that wrong you'll find out in a week and stop trusting the rest.
What Liquibase actually is
Liquibase is a migration tool. You describe schema evolution as an ordered sequence of changesets in a changelog — XML, YAML, JSON, or raw SQL — and Liquibase applies the ones a target hasn't seen yet, tracking state in a DATABASECHANGELOG table. It's mature, it supports more than 60 database engines, and its paid tiers add policy enforcement, drift detection, rollback machinery, and compliance-grade audit evidence. For a fleet of heterogeneous OLTP databases under SOX or PCI scope, that combination is hard to beat and I'm not going to pretend otherwise.
The model is append-only history. Every change is a new changeset appended to the log:
<changeSet id="2026-07-01-add-region" author="dana">
<sql>ALTER TABLE analytics.public.orders ADD COLUMN region STRING</sql>
</changeSet>
<changeSet id="2026-07-03-widen-amount" author="dana">
<sql>ALTER TABLE analytics.public.orders ALTER COLUMN amount SET DATA TYPE NUMBER(38,4)</sql>
</changeSet>
The changelog is the source of truth about how you got here. That's the design's great strength for auditability — and, on a warehouse at scale, the source of most of its friction.
Where the migration model fits Snowflake awkwardly
Three things rub.
Ordering and merge conflicts. Changesets are globally ordered. Two engineers who both append to the changelog in the same week produce a merge that Git resolves textually but that carries real ordering semantics Git can't see. On a busy analytics repo this becomes a standing tax — you're hand-refereeing changeset order in code review.
The changelog drifts from the schema. After two years the answer to "what does ORDERS look like right now?" isn't in any one file — it's the replay of forty changesets. New engineers can't read the current shape of a table from the repo; they have to reconstruct it. State-based tooling inverts this: the file is the current shape.
Risk scanning is pattern-match, not reasoning. Liquibase's policy/quality checks are, at core, a lint pass — they match DDL against configured patterns and flag hits. That catches "you dropped a table." It does not reason about this change against this object's recoverability on this platform. On Snowflake that gap is the whole ballgame, and I'll show why below.
State-based instead of migration-based
SDT doesn't want your history. It wants your destination. You keep one desired-state .sql file per object — the CREATE as you want it to be — and the compare engine diffs that against the live account and computes the migration itself. The same two changes above stop being appended changesets and become a straight edit to one file:
-- analytics/public/orders.sql — the desired state, not a delta
CREATE OR ALTER TABLE analytics.public.orders (
order_id NUMBER(38,0) NOT NULL,
amount NUMBER(38,4), -- widened here, in place
region STRING, -- added here, in place
created_at TIMESTAMP_NTZ
);
You edit the file to say what you want. sdt compare figures out that reaching it means one ADD COLUMN and one type change, in the safe order, and shows you that plan before anything runs:
$ sdt compare --project ./Warehouse --target prod
~ TABLE ANALYTICS.PUBLIC.ORDERS
+ ADD COLUMN region STRING [WARNING]
~ ALTER COLUMN amount NUMBER(38,2) -> NUMBER(38,4) [WARNING]
2 changes, 0 destructive — safe to publish.
There's no changelog to order, no merge referee, and the repo always reads as the current schema because the file is the current schema. The tradeoff is real and worth naming: you give up the narrated, append-only history that an auditor can walk changeset by changeset. If that history is a compliance requirement for you, weight it heavily.
The safety layer is the actual difference
Every change the compare engine emits gets one of four verdicts, and each verdict carries a written reason for that finding:
- UNRECOVERABLE — the platform has no way to give the data back. Publish refuses by default.
- DESTRUCTIVE — data loss, but Time Travel can restore it (a column drop).
- EXPENSIVE — a full table rebuild or long-running rewrite.
- WARNING — additive or cosmetic; safe.
The distinction that pattern-matching misses is between DESTRUCTIVE and UNRECOVERABLE, and on Snowflake it's the difference between a bad afternoon and a permanent data gap. Drop a column and Time Travel gets it back — DESTRUCTIVE. Drop a stream and there is nothing to restore: a stream stores an offset, not rows, and UNDROP STREAM does not exist. A lint rule that flags both as "drop detected" tells you nothing useful; the reasoning is in the classification.
$ sdt compare --project ./Warehouse --target prod
- STREAM ANALYTICS.PUBLIC.ORDERS_STREAM [UNRECOVERABLE]
A stream stores a table-version offset, not data. UNDROP does not
apply and Time Travel cannot rebuild the cursor. Drain and cut over
before removing.
1 UNRECOVERABLE change — publish refused.
To proceed: --allowUnrecoverableDrop
Narrowing a column type earns its own reasoning too. NUMBER(38,4) -> NUMBER(38,2) silently truncates every existing value on rewrite; the classifier flags it DESTRUCTIVE with the reason spelled out, rather than waving through a syntactically valid ALTER. The point isn't that Liquibase can't be configured to catch a specific pattern — it's that a per-change classifier that reasons about reversibility catches the ones nobody thought to write a rule for.
Cross-platform, offline, Apache-2.0 core
Three things that fall out of how SDT is built, not features bolted on:
- One engine for Snowflake and Databricks. The same declarative model and classifier drives DDT for Databricks Unity Catalog. If your platform is both, that's one tool and one mental model instead of two integrations.
- It runs air-gapped. Compare, safe deploy, extract, lint, and lineage are local — no cloud round-trip, no telemetry required, works behind a VPN or in a disconnected environment.
- The classifier is in the free core. Compare, safe deploy, extract, lint, and lineage are Apache-2.0. The safety layer that refuses data loss isn't gated behind a paid tier.
When you should keep Liquibase
Genuinely — several cases where Liquibase is the right call and SDT isn't:
- You manage many database engines. Sixty-plus supported databases is real breadth. SDT does Snowflake and Databricks and nothing else. If Postgres, MySQL, Oracle, and SQL Server are all in scope, that's Liquibase's home turf.
- You need compliance-grade audit trails. A signed, changeset-by-changeset history that an auditor can walk is Liquibase's design center. State-based tooling doesn't reproduce that narrated ledger.
- You're already all-in on changelogs. If your team has years of changelog discipline across many databases and it works, the migration cost of switching may exceed the benefit for the Snowflake slice alone.
None of those are hedges. If you're in one of them, Liquibase is the mature, correct choice and I'd tell you so directly.
If you want to try the other model
You don't have to rewrite anything to look. Export your cumulative schema from Liquibase and import it as desired-state files:
$ liquibase update-sql --output-file=out.sql
$ sdt schema import-ddl out.sql
That gives you a desired-state project you can diff against a live account to see what the state-based plan — and the safety verdicts — look like on your real schema. The deterministic core is free, runs offline, and installs in one line:
npm i -g @sdt-tools/cli
Run sdt compare against a dev account and read the verdicts. If they tell you something a lint pass wouldn't have, that's the case for the switch. If they don't, you've lost ten minutes and Liquibase keeps the job.
Independent tool, not affiliated with or endorsed by Snowflake Inc. or Liquibase Inc. “Snowflake” is a trademark of Snowflake Inc.; “Liquibase” is a trademark of Liquibase Inc.
Ship schema changes against Snowflake or Databricks?
SDT and DDT put a safety classifier on every change — data-loss operations refuse by default. Free tier, runs local-only.
Install SDT Install DDT See pricing