Merge conflicts in changelog/migration files happen for one reason: multiple people append to the same “central” file or reuse the same version/id space. Fix that by making migrations append-only, isolated, and deterministic.
1) Stop editing a single “big” changelog
Liquibase
Use a master changelog that only includes other files, and make teams add new files, not touch master (or touch it in a predictable way).
Pattern
db/changelog/db.changelog-master.yamldb/changelog/changes/2026/01/2026-01-20_001_add_users_email.yaml2026-01-20_002_idx_users_email.yaml
Master file includes via directory (depending on your approach/tooling), or a minimal “include list” maintained in stable order.
Why it helps: you merge new files, not edits in the same lines.
Flyway
Flyway is already “file-per-migration”. Conflicts mostly come from version collisions (two people both create V42__...sql). Fix the version strategy (see below).
2) Use a version/id naming scheme that can’t collide
For Flyway: avoid “same number” collisions
Pick one of these:
A) Timestamp versions (best for teams)
V20260120_1130__add_email_verified.sql- Pro: practically zero collisions
- Con: ordering is chronological; you must handle “two migrations at same minute” by including seconds or a sequence.
B) Team allocation ranges
- Team A uses
V1000–V1999, Team B usesV2000–V2999 - Pro: deterministic
- Con: admin overhead, ugly over time
C) “Next version” enforced by tooling
- Pre-commit hook or CI check rejects duplicate versions.
- Pro: works with sequential numbering
- Con: still causes conflicts locally if people create same next number before rebasing
For Liquibase: changeset IDs must be unique
Typical robust pattern:
id: 2026-01-20-1130-add-email-verifiedauthor: stanley(or team/service)- Keep IDs globally unique across repo.
3) One change = one migration file
Don’t mix unrelated changes in the same file.
- ✅
add column - ✅
backfill data - ✅
add index - ✅
constraint
Each is its own migration/changeset (or at least a tight group that always must run together).
Why: reduces the chance two people touch the same file.
4) Keep “include order” stable and deterministic
If you must edit a master file:
- Always append at the end.
- Or group by folder and keep lexicographical order.
Better: include an entire directory where file name ordering is deterministic (works well if everyone follows naming rules).
5) Use “rebase-first” workflow for migration branches
Before you push:
- rebase onto latest
main - resolve conflicts immediately
- ensure migrations still apply in order
This is boring, but it prevents last-minute “Friday deploy conflict party”.
6) Guardrails in CI
Add checks that fail early:
- Flyway: duplicate versions, version gaps if you disallow them,
validatemust pass. - Liquibase: duplicate
(id, author, path)collisions;validatemust pass. - Run migrations from scratch on an empty DB + optionally from a seeded baseline.
7) Avoid generating migrations automatically in shared files
If you use IDE auto-generation, ensure it generates new file per change and does not rewrite previous migration scripts.
8) Repo layout tricks that work in real life
- Per service:
service-a/db/migration,service-b/db/migration(isolates teams) - Per feature/team folders inside one service (still deterministic ordering by name)
- Separate “schema” vs “data” migrations (data ones conflict more and are riskier)
“Interview-quality” answer (say this)
“We avoid merge conflicts by making migrations append-only and file-based: each change is a new migration file and we don’t edit a shared changelog. For Flyway we use timestamp-based versions to prevent version collisions; for Liquibase we keep a master changelog that includes directories and changeset IDs are globally unique. CI runs validate and rejects duplicate versions/IDs, and devs rebase before pushing.”