PlanetScale — Database branching: three-way merge for schema changes¶
Summary¶
Shlomi Noach (PlanetScale / Vitess) describes three-way
merge for schema changes — the mechanism PlanetScale uses to
detect conflicts between concurrent
deploy requests submitted against the
same production branch. The concept is lifted from Git's
three-way merge but executed on semantic SQL diffs (ALTER
TABLE …, CREATE TABLE …) rather than textual patches. The
core test is diff composition commutativity: for two diffs
diff1 = diff(main, branch1) and diff2 = diff(main, branch2),
the merge is conflict-free iff both diff1(diff2(main)) and
diff2(diff1(main)) are valid and equal. When they differ
(column-order mismatch, for example) or either is invalid
(two columns with same name, different types), the system
reports a conflict. Identical overlapping changes (both
branches add the same name column) are recognised and
auto-adapted rather than flagged. The algorithm runs at
deploy-request submission time, giving developers an
early warning rather than discovering the conflict after
hours of queue wait.
Key takeaways¶
-
Three-way merge terminology is explicitly borrowed from Git, but implementation is completely different. Git merges text; schema merge operates on SQL DDL statements generated by Vitess's
schemadifflibrary."It's similar in concept, but completely different in implementation." (Source: sources/2026-04-21-planetscale-database-branching-three-way-merge-for-schema-changes)
-
The semantic SQL diff replaces textual diff. The raw
git diffof twoCREATE TABLEdefinitions is not executable against a database. PlanetScale instead emits an equivalentALTER TABLE … ADD COLUMN …statement — the semantic diff that can actually run. The textual diff is replaced by "a semantic SQL diff" throughout the deploy flow. -
Conflict detection as function composition. Noach formalises: "Compute
diff1asdiff(main, branch1)… We can considerdiff1as a function — i.e.,diff1(main) => branch1. Likewise, computediff2asdiff(main, branch2)." The three-way-merge test is then: -
If
diff1(diff2(main))is invalid → conflict. - If
diff2(diff1(main))is invalid → conflict. - If both are valid but
diff1(diff2(main)) != diff2(diff1(main))→ conflict. - If both are valid and equal → no conflict.
This is a commutativity check over diff composition.
-
Column-order conflicts are real. Both branches add different columns to
customer, both as the last column. "The order of columns in a table matters. Queries that run aSELECT * FROM customerand use positional arguments will get different columns at positions3and4. The two branches conflict with each other. This is similar to a Git merge conflict where two branches append different rows to the end of a file." This is the column-order conflict — semantically equivalent changes still conflict because MySQL cares about physical ordering. Fix: one branch must anchor the new column withAFTER <existing-column>to make ordering deterministic. -
Index ordering is explicitly disregarded. The same commutativity logic would flag a conflict when two branches add indexes in different orders:
"The only change is the output of
SHOW CREATE TABLEas well asINFORMATION_SCHEMAintrospection. PlanetScale disregards index ordering." Design judgement: even thoughdiff1(diff2(main)) != diff2(diff1(main))on index order, the execution semantics of every query are unchanged — so the commutativity check exempts index order. This is a worked example of operator judgement overriding the naive mathematical check. -
Identical partial overlap is auto-adapted, not flagged. When branch1 and branch2 both add the same
namecolumn (plus unrelated other changes), the three-way-merge treats that as overlap:"Given that both branches completely agree on that particular change, PlanetScale's three-way merge considers this as an overlap and allows it. Should
branch1merge first,branch2's diff auto-adapts and is left to the creation oftbl2only." Mechanism:schemadiffformalises each diff (ALTER,CREATE, …) individually, so matching sub-diffs can be set-subtracted from the second branch's diff set after the first merges. -
Early conflict warning at queue admission. "When a developer submits their deploy request, their change is validated against all queued changes. This avoids the situation where the developer waits for hours in queue, only to learn the one deployment before theirs caused a conflict. PlanetScale shoots an early warning so that developers can better use their time in queue." This is the early queue-admission warning — conflict is shifted left from cutover to submit.
-
Nothing tracks intermediate branch state. "It's worth pointing out that nothing tracks the changes on a development branch while it's open. Dev 1 may
CREATE,ALTER, andDROPall they want." The system only computes the diff when the deploy request is created, comparing the current branch schema tomain. No replay of intermediate states is required — all logic is a pure function of two end states.
Systems¶
- systems/planetscale — product / company
- systems/vitess — MySQL sharding substrate
- systems/vitess-schemadiff — the Vitess library that computes semantic SQL diffs and enforces the three-way merge
- systems/mysql — underlying database, source of column-order semantics
- systems/git — the source of the three-way-merge concept that is being adapted
Concepts¶
- concepts/schema-three-way-merge — the central concept
- concepts/semantic-schema-diff — SQL DDL as the diff unit, replacing textual diff
- concepts/diff-commutativity-check —
diff1(diff2(main)) == diff2(diff1(main))test - concepts/column-order-conflict — structurally-identical add-column diffs still conflict on physical ordering
- concepts/diff-overlap-auto-adaptation — identical partial overlaps are treated as benign
- concepts/queue-admission-conflict-warning — shift conflict reporting left to submit time
Patterns¶
- patterns/three-way-merge-for-schema-changes — the overall algorithmic pattern
- patterns/early-queue-conflict-warning — warn developers at queue admission rather than at cutover
- patterns/branch-based-schema-change-workflow — the enclosing workflow this algorithm plugs into
Numbers and operational details¶
- The post does not disclose production numbers (N of concurrent branches per tenant, CPU/memory cost of the commutativity check, algorithmic complexity of the diff composition). It is a conceptual post walking through the algorithm with a customer-facing framing.
- The worked examples use a
customer(id, [name], [subscription_type], [joined_at])table and adelivery(id, customer_id)table — canonical small-scale toy examples. - Algorithm is described as "more elaborate than described thus far" — the post presents a simplified core with named extensions (index-order exemption, identical-overlap auto-adapt, pre-queue conflict warning). Extensions beyond these are not enumerated.
Caveats¶
- Not all conflicts are detected by mechanical
commutativity. The post only mentions structural
conflicts (column-name collision, column-order
mismatch). Semantic conflicts that don't show up in
DDL (e.g., a new
NOT NULLcolumn plus an old application-level assumption of nullable) are out-of-scope forschemadiff's check. - Index-ordering exemption is a design choice, not a mathematical property. PlanetScale chose to disregard index order because query semantics are unchanged. A different schema-diff tool could surface it as a conflict. This is a policy layer on top of the commutativity primitive.
- The post frames three-way merge as a pre-deploy
conflict detector, not a merge commit synthesiser.
Unlike Git, the system does not produce a
diff_merged = merge(diff1, diff2)that can be run once. Theschemadiff-driven deploy pipeline runs each branch's diff separately, in series, in queue-submission order — with the three-way-merge check as admission control. - Applies to schema, not data. Data conflicts (two branches write different values to the same row) are outside scope. PlanetScale's branching model snapshots the schema, not the data; data merges are a separate (unsolved-in-this-post) problem.
- Origin 2023-04-26, re-fetched 2026-04-21. The algorithm has been in production for roughly three years by the time of this wiki ingest; the post remains the canonical public description of the mechanism.
Source¶
- Original: https://planetscale.com/blog/database-branching-three-way-merge-schema-changes
- Raw markdown:
raw/planetscale/2026-04-21-database-branching-three-way-merge-for-schema-changes-4e94c36b.md
Related¶
- companies/planetscale
- systems/planetscale
- systems/vitess
- systems/vitess-schemadiff
- concepts/schema-three-way-merge
- concepts/semantic-schema-diff
- concepts/diff-commutativity-check
- concepts/column-order-conflict
- concepts/diff-overlap-auto-adaptation
- concepts/queue-admission-conflict-warning
- concepts/database-branching
- concepts/deploy-request
- concepts/schema-change-queue
- concepts/pre-flight-schema-conflict-check
- patterns/three-way-merge-for-schema-changes
- patterns/early-queue-conflict-warning
- patterns/branch-based-schema-change-workflow