# 15. Production checklist [← 14. Running both at once](14-running-both-at-once.md) · [Index](../README.md) 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`](../src/main/java/com/ankurm/dbmigrations/web/MigrationDiagnosticsController.java) 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](11-liquibase-locking.md) (Liquibase's own summary said "successful" while the table stayed empty). Run any profile and hit `/diag/migrations`: ```java @GetMapping("/diag/migrations") public Map migrations() { Map 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`](../src/main/java/com/ankurm/dbmigrations/web/MigrationDiagnosticsController.java)) The `flyway_schema_history` query is quoted lowercase on purpose — chapter [2](02-anatomy-of-a-migration-run.md) 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. /diag/migrations plain JDBC, no library API what actually landed not what the library thinks happened Chapter 11's phantom success is exactly the gap this endpoint is built to close — a tool's own "successful" summary and the database's actual state can disagree. ## The checklist Every line below is something this module actually reproduced, not a generic best practice: 1. **Never run unquoted queries against Flyway's tracking table on H2** — `flyway_schema_history` is created and queried by Flyway itself using quoted lowercase identifiers, so an unquoted query folds to uppercase and finds nothing (chapter [2](02-anatomy-of-a-migration-run.md)). 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](03-checksum-validation.md)). 3. **Out-of-order migrations fail the whole startup by default**, not just the late migration — set `outOfOrder=true` deliberately if parallel branches will ever land migrations non-sequentially (chapter [4](04-out-of-order-migrations.md)). 4. **`baselineVersion` defaults 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](06-baselining-an-existing-database.md), chapter [14](14-running-both-at-once.md)). 5. **Flyway Community's `undo` is a stub that throws `FlywayRedgateEditionRequiredException` at runtime** despite compiling fine — there is no working "undo" without a Redgate license (chapter [7](07-why-there-is-no-undo.md)). 6. **Liquibase can only auto-generate a rollback for structural changes** like `createTable` — anything touching data (`insert`, `update`, `delete`) needs an explicit `rollback:` block written and tested when the changeset is written, not discovered missing during an incident (chapter [10](10-rollback-auto-generated-vs-explicit.md)). 7. **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](11-liquibase-locking.md)). 8. **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](13-the-fsl-license-change.md)). 9. **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](14-running-both-at-once.md)). 10. **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](https://documentation.red-gate.com/fd/configuration-184127302.html) (`rel="nofollow"`) — every property this module exercised, plus the ones it didn't. - [Liquibase configuration reference](https://docs.liquibase.com/parameters/home.html) (`rel="nofollow"`) — including the lock-related settings from chapter [11](11-liquibase-locking.md). - The module's own [`README.md`](../README.md) indexes every chapter, every test, and every captured transcript this checklist draws on.