diff --git a/db-migrations-flyway-liquibase/README.md b/db-migrations-flyway-liquibase/README.md index d8cea00..77b076e 100644 --- a/db-migrations-flyway-liquibase/README.md +++ b/db-migrations-flyway-liquibase/README.md @@ -87,6 +87,12 @@ helper — the tests assert the same numbers they print, so a transcript going s | [`14-liquibase-oss-license-service.txt`](docs/output/14-liquibase-oss-license-service.txt) | [`LiquibaseLicenseServiceTest`](src/test/java/com/ankurm/dbmigrations/liquibase/LiquibaseLicenseServiceTest.java) | | [`15-both-together-same-datasource.txt`](docs/output/15-both-together-same-datasource.txt) | [`BothTogetherTest`](src/test/java/com/ankurm/dbmigrations/BothTogetherTest.java) | +One file is the exception: [`16-liquibase-lock-defaults-javap.txt`](docs/output/16-liquibase-lock-defaults-javap.txt) +isn't produced by `mvn test` — it's the trimmed `javap -p -c -constants` output confirming +Liquibase's default lock-poll and lock-wait settings straight from `liquibase-core`'s bytecode +(see [chapter 11](docs/11-liquibase-locking.md)), captured by hand since there's no JVM assertion +that reads a compiled class's own constant pool. + ## Findings worth the trip - Flyway Community's `undo`, `diff`, `check`, `deploy`, `generate`, `model`, `prepare` and `auth` commands all **compile fine** and throw `FlywayRedgateEditionRequiredException` only at runtime — there is no working rollback in Flyway Community at all ([chapter 7](docs/07-why-there-is-no-undo.md)). diff --git a/db-migrations-flyway-liquibase/docs/01-the-problem-and-mental-model.md b/db-migrations-flyway-liquibase/docs/01-the-problem-and-mental-model.md index a9bb775..a448568 100644 --- a/db-migrations-flyway-liquibase/docs/01-the-problem-and-mental-model.md +++ b/db-migrations-flyway-liquibase/docs/01-the-problem-and-mental-model.md @@ -70,6 +70,6 @@ you can watch a real startup's bookkeeping instead of only reading about it. ## Going deeper -- [Flyway: how migrations work](https://documentation.red-gate.com/flyway/flyway-cli-and-api/concepts/migrations) — the vendor's own concept page (`rel="nofollow"`). +- [Flyway: how migrations work](https://documentation.red-gate.com/fd/migrations-271585107.html) — the vendor's own concept page (`rel="nofollow"`). - [Liquibase: how it works](https://docs.liquibase.com/concepts/introduction-to-liquibase.html) — the vendor's own concept page (`rel="nofollow"`). - [Spring Boot 4.1 release notes](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.1-Release-Notes) — for the package relocation both tools' autoconfiguration went through (chapter [2](02-anatomy-of-a-migration-run.md) touches this). diff --git a/db-migrations-flyway-liquibase/docs/02-anatomy-of-a-migration-run.md b/db-migrations-flyway-liquibase/docs/02-anatomy-of-a-migration-run.md index aeddd1e..5465268 100644 --- a/db-migrations-flyway-liquibase/docs/02-anatomy-of-a-migration-run.md +++ b/db-migrations-flyway-liquibase/docs/02-anatomy-of-a-migration-run.md @@ -95,5 +95,5 @@ raw report query against a Flyway-managed schema. ## Going deeper -- [Flyway migration naming](https://documentation.red-gate.com/flyway/flyway-cli-and-api/concepts/migrations#naming) — the full naming grammar, including undo and repeatable prefixes (`rel="nofollow"`). +- [Flyway migration naming](https://documentation.red-gate.com/fd/migrations-271585107.html) — the full naming grammar, including undo and repeatable prefixes (`rel="nofollow"`). - H2's identifier case sensitivity is documented on the [H2 SQL grammar page](https://h2database.com/html/grammar.html#name) under `DATABASE_TO_LOWER`/`DATABASE_TO_UPPER` (`rel="nofollow"`) — this module doesn't set either, so it runs on H2's default. diff --git a/db-migrations-flyway-liquibase/docs/03-checksum-validation.md b/db-migrations-flyway-liquibase/docs/03-checksum-validation.md index f185efb..f3ae121 100644 --- a/db-migrations-flyway-liquibase/docs/03-checksum-validation.md +++ b/db-migrations-flyway-liquibase/docs/03-checksum-validation.md @@ -43,4 +43,4 @@ themselves, not a schema-drift detector. ## Going deeper - [`spring.flyway.validate-on-migrate`](https://docs.spring.io/spring-boot/appendix/application-properties/index.html#application-properties.data-migration.spring.flyway.validate-on-migrate) in the Spring Boot configuration reference (`rel="nofollow"`) — `true` by default. -- [Flyway's `repair` command](https://documentation.red-gate.com/flyway/flyway-cli-and-api/commands/repair) (`rel="nofollow"`) — what actually happens to `flyway_schema_history` when you run it, and why it's a deliberate, logged action rather than something to script into a startup hook. +- [Flyway's `repair` command](https://documentation.red-gate.com/flyway/reference/commands/repair) (`rel="nofollow"`) — what actually happens to `flyway_schema_history` when you run it, and why it's a deliberate, logged action rather than something to script into a startup hook. diff --git a/db-migrations-flyway-liquibase/docs/04-out-of-order-migrations.md b/db-migrations-flyway-liquibase/docs/04-out-of-order-migrations.md index c027c85..d32d7e1 100644 --- a/db-migrations-flyway-liquibase/docs/04-out-of-order-migrations.md +++ b/db-migrations-flyway-liquibase/docs/04-out-of-order-migrations.md @@ -64,5 +64,5 @@ installed_rank | version | description | success ## 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. +- [`ignoreMigrationPatterns`](https://documentation.red-gate.com/fd/flyway-ignore-migration-patterns-setting-277579002.html) (`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. diff --git a/db-migrations-flyway-liquibase/docs/05-repeatable-migrations.md b/db-migrations-flyway-liquibase/docs/05-repeatable-migrations.md index 8c1e435..68eda63 100644 --- a/db-migrations-flyway-liquibase/docs/05-repeatable-migrations.md +++ b/db-migrations-flyway-liquibase/docs/05-repeatable-migrations.md @@ -58,5 +58,5 @@ filename prefix. ## Going deeper - **Repeatable migrations run last, after every pending versioned one**, in the order they appear on the classpath (alphabetically, by default) — not interleaved by when they were last changed. -- [Flyway's repeatable migration docs](https://documentation.red-gate.com/flyway/flyway-cli-and-api/concepts/migrations#repeatable-migrations) (`rel="nofollow"`) cover ordering and the `installedOn`/checksum comparison in full. +- [Flyway's repeatable migration docs](https://documentation.red-gate.com/fd/repeatable-migrations-273973335.html) (`rel="nofollow"`) cover ordering and the `installedOn`/checksum comparison in full. - A view is the textbook use case, but the same mechanism works for anything idempotent — a stored procedure body, or a `MERGE`/`upsert` of reference data that should reflect whatever the file currently says, not whatever it said the first time it ran. diff --git a/db-migrations-flyway-liquibase/docs/06-baselining-an-existing-database.md b/db-migrations-flyway-liquibase/docs/06-baselining-an-existing-database.md index 34f320d..3a6c271 100644 --- a/db-migrations-flyway-liquibase/docs/06-baselining-an-existing-database.md +++ b/db-migrations-flyway-liquibase/docs/06-baselining-an-existing-database.md @@ -52,5 +52,5 @@ assertion or a broken monitoring query — this module's own first draft did exa ## Going deeper - **`baselineVersion` decides what "already accounted for" means**, and it matters which value you pick — chapter [14](14-running-both-at-once.md) shows the same mechanism used with `baseline-version=0` instead of `1`, for a database whose existing schema *doesn't* match any of your migration files at all. -- [Flyway `baseline` command reference](https://documentation.red-gate.com/flyway/flyway-cli-and-api/commands/baseline) (`rel="nofollow"`) — the one-time `flyway baseline` CLI/API call this test's `baselineOnMigrate=true` triggers automatically on first startup. +- [Flyway `baseline` command reference](https://documentation.red-gate.com/flyway/reference/commands/baseline) (`rel="nofollow"`) — the one-time `flyway baseline` CLI/API call this test's `baselineOnMigrate=true` triggers automatically on first startup. - Baselining is a one-way door in the sense that matters: once version 1 is marked as baselined, Flyway will never again check whether the schema it describes actually matches `V1__init.sql`'s content — that specific file's checksum is simply never looked at again for this database. diff --git a/db-migrations-flyway-liquibase/docs/08-concurrent-startup-and-locking.md b/db-migrations-flyway-liquibase/docs/08-concurrent-startup-and-locking.md index a4ea19d..967ba32 100644 --- a/db-migrations-flyway-liquibase/docs/08-concurrent-startup-and-locking.md +++ b/db-migrations-flyway-liquibase/docs/08-concurrent-startup-and-locking.md @@ -53,5 +53,5 @@ external coordinator, no separate lock table: the schema history table *is* the ## Going deeper - **A naive assertion here would compare wall-clock time against the sum of both individual durations**, expecting serialization to look like "one after the other, end to end". That's the wrong model for two threads submitted at the same instant with one blocking on the other's lock — the right check is that *both* individual durations are long, proving the loser genuinely waited rather than racing ahead. This module's test was rewritten once to fix exactly that reasoning error. -- [Flyway's locking strategy](https://documentation.red-gate.com/flyway/flyway-cli-and-api/concepts/migrations#concurrent-migration) (`rel="nofollow"`) documents the row-lock approach and which databases support it natively versus via a fallback. +- [Flyway's locking strategy](https://documentation.red-gate.com/fd/migrations-271585107.html) (`rel="nofollow"`) documents the row-lock approach and which databases support it natively versus via a fallback. - Liquibase solves the same problem with a dedicated, separate lock table rather than a row lock on the history table itself — chapter [11](11-liquibase-locking.md) measures how differently that behaves under contention. diff --git a/db-migrations-flyway-liquibase/docs/10-rollback-auto-generated-vs-explicit.md b/db-migrations-flyway-liquibase/docs/10-rollback-auto-generated-vs-explicit.md index 439f692..569c8ba 100644 --- a/db-migrations-flyway-liquibase/docs/10-rollback-auto-generated-vs-explicit.md +++ b/db-migrations-flyway-liquibase/docs/10-rollback-auto-generated-vs-explicit.md @@ -78,6 +78,6 @@ ID | EVENT ## Going deeper -- [Which change types Liquibase can auto-generate a rollback for](https://docs.liquibase.com/workflows/liquibase-community/how-to-apply-or-revert-changes.html) (`rel="nofollow"`) — `createTable`, `addColumn`, and a handful of other structural changes; almost anything involving data (`insert`, `update`, `delete`, most `sql:` changes) needs an explicit `rollback:` block. +- [Which change types Liquibase can auto-generate a rollback for](https://docs.liquibase.com/community/user-guide-5-0/what-automatic-rollbacks-does-liquibase-support) (`rel="nofollow"`) — `createTable`, `addColumn`, and a handful of other structural changes; almost anything involving data (`insert`, `update`, `delete`, most `sql:` changes) needs an explicit `rollback:` block. - **A team relying on rollback in production is really relying on discipline**: every changeset that touches data needs its rollback written and tested *at the time the changeset is written*, not discovered missing during an actual incident — `rollback(1, ...)` failing is the worst possible moment to learn `insert` has no inverse. - Liquibase also supports `rollbackCount`, `rollbackToDate` and rolling back by tag — this module exercises only the single-changeset `rollback(int, ...)` overload. diff --git a/db-migrations-flyway-liquibase/docs/11-liquibase-locking.md b/db-migrations-flyway-liquibase/docs/11-liquibase-locking.md index c5b2f92..879c77e 100644 --- a/db-migrations-flyway-liquibase/docs/11-liquibase-locking.md +++ b/db-migrations-flyway-liquibase/docs/11-liquibase-locking.md @@ -26,14 +26,18 @@ ID | AUTHOR | EXECTYPE The changeset ran exactly once — same guarantee as Flyway. The *shape* of the wait is different, though, and it's a real, verified difference: instance B's call took over ten seconds, for 800ms of underlying work. That's not noise. Liquibase's `LockService` doesn't retry immediately when a -lock is held — it polls, and `liquibase.changeLogLockPollRate`'s default, confirmed by reading -`GlobalConfiguration`'s own bytecode, is **10 seconds**: +lock is held — it polls, and `liquibase.changeLogLockPollRate`'s default, confirmed by decompiling +`GlobalConfiguration`'s own bytecode with `javap`, is **10 seconds**: ``` -liquibase.changeLogLockPollRate → default 10 (seconds between checks while the lock is held) -liquibase.changeLogLockWaitTimeInMinutes → default 5 (minutes before giving up entirely) +liquibase.changeLogLockPollRate -> default 10 (seconds between checks while the lock is held) +liquibase.changeLogLockWaitTimeInMinutes -> default 5 (minutes before giving up entirely) ``` +(from [`docs/output/16-liquibase-lock-defaults-javap.txt`](output/16-liquibase-lock-defaults-javap.txt), +the trimmed `javap -p -c -constants` output showing both `long` constants — `5` and `10` — right next +to the field they initialize) + instance A diff --git a/db-migrations-flyway-liquibase/docs/12-the-oss-license-service.md b/db-migrations-flyway-liquibase/docs/12-the-oss-license-service.md index ffb8060..92ef7c8 100644 --- a/db-migrations-flyway-liquibase/docs/12-the-oss-license-service.md +++ b/db-migrations-flyway-liquibase/docs/12-the-oss-license-service.md @@ -32,4 +32,4 @@ Community jar. ## Going deeper - This test is a snapshot of Liquibase 5.0.3's Community classpath, not a guarantee about future releases — the license service's existence at all, brand new in 5.0, is itself evidence that Liquibase's commercial boundary is actively being redrawn (chapter [13](13-the-fsl-license-change.md) covers why). -- [Liquibase Pro feature comparison](https://www.liquibase.com/liquibase-pricing) (`rel="nofollow"`) — what Pro actually adds, as separate functionality rather than unlocked stubs. +- [Liquibase Pro feature comparison](https://www.liquibase.com/pricing) (`rel="nofollow"`) — what Pro actually adds, as separate functionality rather than unlocked stubs. diff --git a/db-migrations-flyway-liquibase/docs/output/16-liquibase-lock-defaults-javap.txt b/db-migrations-flyway-liquibase/docs/output/16-liquibase-lock-defaults-javap.txt new file mode 100644 index 0000000..867809e --- /dev/null +++ b/db-migrations-flyway-liquibase/docs/output/16-liquibase-lock-defaults-javap.txt @@ -0,0 +1,25 @@ +================================================================================ +Liquibase: confirming the default lock-poll and lock-wait settings via javap +================================================================================ +captured: 2026-09-15T07:07:00.000000000Z + +$ unzip -o -q liquibase-core-5.0.3.jar 'liquibase/GlobalConfiguration*.class' -d extracted +$ javap -p -c -constants -classpath extracted liquibase.GlobalConfiguration + +-- static initializer, defining CHANGELOGLOCK_WAIT_TIME -- + 69: ldc #88 // String changelogLockWaitTimeInMinutes + 76: ldc #92 // String liquibase.changeLogLockWaitTimeInMinutes + 81: ldc #94 // String Number of minutes to wait for the changelog lock to be available before giving up + 86: ldc2_w #96 // long 5l + 98: putstatic #101 // Field CHANGELOGLOCK_WAIT_TIME:Lliquibase/configuration/ConfigurationDefinition; + +-- static initializer, defining CHANGELOGLOCK_POLL_RATE -- + 102: ldc #104 // String changelogLockPollRate + 109: ldc #106 // String liquibase.changeLogLockPollRate + 114: ldc #108 // String Number of seconds wait between checks to the changelog lock when it is locked + 119: ldc2_w #110 // long 10l + 131: putstatic #112 // Field CHANGELOGLOCK_POLL_RATE:Lliquibase/configuration/ConfigurationDefinition; + +-- reading the two "long N" constants above off the bytecode directly -- +liquibase.changeLogLockPollRate -> default 10 (seconds between checks while the lock is held) +liquibase.changeLogLockWaitTimeInMinutes -> default 5 (minutes before giving up entirely)