Add db-migrations-flyway-liquibase: Flyway vs Liquibase migrations, rollbacks and baselines on Spring Boot 4.1
Companion code for the Flyway vs Liquibase article: checksum validation, out-of-order and repeatable migrations, baselining an existing schema, Flyway Community's undo/diff/deploy stubs, concurrent-startup locking for both tools, Liquibase changeset identity and rollback (auto-generated vs explicit), a verified Liquibase 5.0.3 filename-caching defect, the new OSS license service, the FSL license change, and running both tools against one database. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Q6XdRjtsp4862EM44T7i9a
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
# 4. Out-of-order migrations
|
||||
|
||||
[← 3. Checksum validation](03-checksum-validation.md) · [Index](../README.md) · Next: [5. Repeatable migrations →](05-repeatable-migrations.md)
|
||||
|
||||
Two branches, both adding "the next migration": one ships `V3` and merges quickly, the other's
|
||||
`V2` sits in a slow-to-review pull request and lands afterward. By the time `V2` merges, every
|
||||
environment that deployed the first branch already has `V3` applied. What happens when `V2`
|
||||
finally shows up?
|
||||
|
||||
[`FlywayOutOfOrderTest`](../src/test/java/com/ankurm/dbmigrations/flyway/FlywayOutOfOrderTest.java)
|
||||
reproduces exactly this: `V1` and `V3` apply on a first startup, then `V2__add_note_length_check.sql`
|
||||
is dropped in afterward, and a second startup runs against the same database.
|
||||
|
||||
**The common assumption — that Flyway just quietly skips the late-arriving lower version and
|
||||
carries on — is wrong.** The whole application fails to start:
|
||||
|
||||
```
|
||||
context failed to start: FlywayValidateException: Validate failed: Migrations have failed validation
|
||||
Detected resolved migration not applied to database: 2.
|
||||
To ignore this migration, set -ignoreMigrationPatterns='*:ignored'. To allow executing this migration, set -outOfOrder=true.
|
||||
```
|
||||
|
||||
(from [`docs/output/03-flyway-out-of-order.txt`](output/03-flyway-out-of-order.txt); this test was
|
||||
originally written expecting the silent-skip behaviour, and the real run corrected it)
|
||||
|
||||
Validation runs before migration, and by default (`spring.flyway.out-of-order=false`) a resolved
|
||||
migration with a version lower than the highest already-applied one is treated as a validation
|
||||
failure, not a no-op. Nothing gets applied and the context never comes up.
|
||||
|
||||
<svg viewBox="0 0 700 170" xmlns="http://www.w3.org/2000/svg" font-family="monospace" font-size="13">
|
||||
<text x="20" y="25">history: V1 ✓, V3 ✓</text>
|
||||
<rect x="20" y="40" width="60" height="30" fill="#dcfce7" stroke="#16a34a"/><text x="50" y="60" text-anchor="middle">V1</text>
|
||||
<rect x="90" y="40" width="60" height="30" fill="#dcfce7" stroke="#16a34a"/><text x="120" y="60" text-anchor="middle">V3</text>
|
||||
<rect x="160" y="40" width="60" height="30" fill="#fee2e2" stroke="#dc2626" stroke-dasharray="3 2"/><text x="190" y="60" text-anchor="middle">V2?</text>
|
||||
<text x="260" y="60">out-of-order=false (default)</text>
|
||||
<path d="M420 55 L470 55" stroke="#334155" stroke-width="2" marker-end="url(#a3)"/>
|
||||
<rect x="470" y="35" width="200" height="40" rx="6" fill="#fee2e2" stroke="#dc2626"/>
|
||||
<text x="570" y="60" text-anchor="middle">whole startup fails</text>
|
||||
|
||||
<text x="20" y="110">same history, out-of-order=true</text>
|
||||
<path d="M270 105 L320 105" stroke="#334155" stroke-width="2" marker-end="url(#a3)"/>
|
||||
<rect x="320" y="85" width="220" height="40" rx="6" fill="#dcfce7" stroke="#16a34a"/>
|
||||
<text x="430" y="110" text-anchor="middle">V2 runs, slotted in after V3</text>
|
||||
<text x="20" y="150" font-size="11" fill="#475569">history keeps its real apply order: V1, V3, V2 — not renumbered</text>
|
||||
<defs><marker id="a3" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#334155"/></marker></defs>
|
||||
</svg>
|
||||
|
||||
Setting `spring.flyway.out-of-order=true` is the fix the error message itself names, and a third
|
||||
startup in the same test confirms what it actually does: `V2` runs, and the history table keeps it
|
||||
in *real* application order — rank 3, version 2, sitting after rank 2's version 3 — Flyway does not
|
||||
retroactively renumber or reorder anything:
|
||||
|
||||
```
|
||||
installed_rank | version | description | success
|
||||
---------------+---------+-------------------------------------------+--------
|
||||
-1 | NULL | << Flyway Schema History table created >> | true
|
||||
1 | 1 | init | true
|
||||
2 | 3 | add note index | true
|
||||
3 | 2 | add note length check | true
|
||||
```
|
||||
|
||||
(from the same [transcript](output/03-flyway-out-of-order.txt))
|
||||
|
||||
## Going deeper
|
||||
|
||||
- **`outOfOrder` is a blunt, global switch** — turning it on doesn't just allow the one late migration you're expecting, it allows *any* lower-numbered migration to slot in from then on. Flyway's own docs call this out as reducing reproducibility, and `docs/output/03-flyway-out-of-order.txt`'s third-startup log line says so verbatim: `outOfOrder mode is active. Migration of schema may not be reproducible.`
|
||||
- [`ignoreMigrationPatterns`](https://documentation.red-gate.com/flyway/flyway-cli-and-api/configuration/parameters/ignore-migration-patterns) (`rel="nofollow"`) — the error message's other suggested fix, for permanently ignoring a specific migration instead of relaxing ordering globally.
|
||||
- A team that hits this regularly is usually missing a CI check that fails a PR when its migration's version number is lower than what's already merged to main — cheaper than relying on `outOfOrder` at all.
|
||||
Reference in New Issue
Block a user