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
7.4 KiB
15. Production checklist
← 14. Running both at once · Index
Every earlier chapter isolated one behavior. This one is the module's diagnostic exhibit and a closing checklist built directly from what the other fourteen chapters actually found — not generic advice, a list where every line traces back to a specific chapter.
The diagnostic endpoint
MigrationDiagnosticsController
is plain JDBC against INFORMATION_SCHEMA plus both tools' tracking tables — no Flyway or
Liquibase Java API call anywhere in it. That's deliberate: it shows what actually landed in the
database, not what either library's in-memory model believes happened, which is exactly the gap
that caused the Liquibase filename defect in chapter 11 (Liquibase's
own summary said "successful" while the table stayed empty). Run any profile and hit
/diag/migrations:
@GetMapping("/diag/migrations")
public Map<String, Object> migrations() {
Map<String, Object> result = new LinkedHashMap<>();
try (Connection conn = dataSource.getConnection()) {
result.put("tables", tableNames(conn));
result.put("flyway_schema_history", rows(conn,
"select \"installed_rank\", \"version\", \"description\", \"type\", \"checksum\", \"success\" "
+ "from \"flyway_schema_history\" order by \"installed_rank\"", true));
result.put("databasechangelog", rows(conn,
"select id, author, filename, orderexecuted, exectype, md5sum "
+ "from databasechangelog order by orderexecuted", true));
}
catch (SQLException ex) {
result.put("error", ex.getMessage());
}
return result;
}
(from MigrationDiagnosticsController.java)
The flyway_schema_history query is quoted lowercase on purpose — chapter
2 covers why an unquoted version of this exact query returns
nothing at all against H2's default case-folding. The controller's own javadoc says it plainly:
delete this before a real deployment — it has no authorization and dumps raw schema-history
rows, including checksums, to anyone who can reach the port.
The checklist
Every line below is something this module actually reproduced, not a generic best practice:
- Never run unquoted queries against Flyway's tracking table on H2 —
flyway_schema_historyis created and queried by Flyway itself using quoted lowercase identifiers, so an unquoted query folds to uppercase and finds nothing (chapter 2). - A checksum mismatch fails startup, full stop — editing an already-applied migration file is caught the instant the app starts, not silently ignored (chapter 3).
- Out-of-order migrations fail the whole startup by default, not just the late migration — set
outOfOrder=truedeliberately if parallel branches will ever land migrations non-sequentially (chapter 4). baselineVersiondefaults to 1, meaning "assume the existing schema already matches V1" — correct only when that's literally true. Get it wrong and Flyway either re-runs migrations that already happened or skips ones that never did (chapter 6, chapter 14).- Flyway Community's
undois a stub that throwsFlywayRedgateEditionRequiredExceptionat runtime despite compiling fine — there is no working "undo" without a Redgate license (chapter 7). - Liquibase can only auto-generate a rollback for structural changes like
createTable— anything touching data (insert,update,delete) needs an explicitrollback:block written and tested when the changeset is written, not discovered missing during an incident (chapter 10). - Liquibase's default lock-poll rate is 10 seconds — a losing instance in a rolling deploy can sit idle for up to ten seconds waiting on sub-second work, which matters if a readiness probe times out sooner (chapter 11).
- Liquibase Community 5.0 ships under the FSL, not Apache 2.0 — ordinary production use is unaffected, but license-compliance tooling that treats "source-available" differently from "open source" will flag it (chapter 13).
- Running both tools against one database means two independent bookkeepers, not integration — each is blind to tables the other owns, and the moment they'd need to touch the same object you're outside anything either one tests for (chapter 14).
- Delete the diagnostics endpoint before shipping — it has no authorization and exists purely so this module's own claims could be checked against a live server.
Should you even do this — pick one, deliberately
Neither tool is "safer" in the abstract; they fail differently, and this module reproduced both failure shapes directly. Flyway's version-ordered, checksum-validated model is easy to reason about and its Community edition simply has no rollback — plan every migration as forward-only from day one. Liquibase's changeset model supports real rollbacks, but only for the change types it knows how to invert, and its 5.0 license change is a real (if narrow) compliance question some organizations will need to route through their own process. Running both together is a workable bridge during a migration between them, never a permanent architecture — pick one, own it, and use this module to see exactly what "own it" has to account for.
Going deeper
- Flyway configuration reference (
rel="nofollow") — every property this module exercised, plus the ones it didn't. - Liquibase configuration reference (
rel="nofollow") — including the lock-related settings from chapter 11. - The module's own
README.mdindexes every chapter, every test, and every captured transcript this checklist draws on.