Core Concepts
|
This section documents schema/database evolution using version-controlled migration tools — Liquibase (relational/SQL), Mongock (MongoDB, Java-native) and Flamingock (Mongock’s successor, generalized to any external system) — as described by each tool’s own official documentation: liquibase.org and docs.liquibase.com for Liquibase, docs.mongock.io for Mongock, and docs.flamingock.io for Flamingock — which are the references these pages are written and verified against. This content was generated with the assistance of AI and should be verified against those official docs before being relied on in production. Mongock is now maintained under the Flamingock GitHub organization (github.com/flamingock/mongock), and Flamingock is positioned as its successor — see Coming from Mongock. Readers starting a new project should default to Flamingock unless they have a specific reason to stay on Mongock. This section’s bibliography lists the reference material consulted while preparing these pages. |
Liquibase, Mongock and Flamingock each use their own name for the same handful of ideas. This page introduces that shared vocabulary once, names which tool calls each concept what, and links each one to an official doc page — so the tool-specific pages that follow can use the terms directly without re-explaining them.
At startup (or on a CLI invocation, for Liquibase), every one of these tools follows the same generic shape: acquire a lock so only one instance migrates at a time, read what has already run from an audit/tracking store, compute what is left to run, execute it in order, and record each execution as it completes.
This is why the lock in that sequence matters: without it, two instances starting at once would both compute the same pending change units and could execute them concurrently.
Change Unit
A change unit is one atomic, uniquely identified piece of migration work — the smallest thing that runs and gets recorded as having run. Liquibase calls it a changeset (What is a Changeset?); Mongock calls it a changeunit (Migration / ChangeUnit reference); Flamingock calls it a change. In every case, identity is the combination of an id, an author (or ordering key) and, for Liquibase, the file it was declared in — change that identity and the tool treats it as a brand-new unit rather than an edit to the old one.
Changelog / Ordering
The changelog is the ordered, append-only list of change units a tool knows about. Liquibase’s changelog is an
explicit file (SQL, XML, YAML or JSON) that lists changesets in the order they appear, optionally pulling in
other files via include/includeAll
(see Liquibase: Changelogs & Changesets).
Mongock and Flamingock instead derive ordering from each @ChangeUnit/@Change class’s declared order (or
equivalent) field, since the "changelog" there is the set of annotated classes on the classpath rather than a
single file. In all three, the rule is the same: change units already recorded as applied are skipped, and new
ones run in ascending order.
Idempotency
A change unit must be safe to define once and run exactly one time against a given target. This matters because
the tool’s own bookkeeping — not the target’s own state — is what decides whether a unit runs again: if a change
unit is edited after it has already been recorded as applied, none of these tools re-runs it automatically (see
Checksums / Hashes). If a change unit’s logic is not naturally idempotent (e.g. INSERT without a guard,
or a MongoDB update that isn’t safe to repeat), and it somehow does run twice — a partially-applied run retried
after a crash, for instance — it must not corrupt data or fail hard the second time. The general fix is to guard
the operation itself: CREATE TABLE IF NOT EXISTS, an upsert instead of an unconditional insert, or an explicit
existence check before acting.
Checksums / Hashes
Liquibase computes a checksum (or hash) of a changeset’s content the first time it runs, and stores it
alongside the record of execution. On every later run, Liquibase recomputes the checksum and compares it: a
mismatch means an already-applied changeset was edited after the fact, which is exactly the situation idempotency
assumptions can’t protect against, because the tool has no way to know whether the edited version was ever
actually run. Liquibase treats this as a validation failure by default and stops (see
Liquibase: Tracking & Locking for how to
resolve one). Mongock and Flamingock have no equivalent check for their code-based change units — identity is
just the declared id, with no content hash, so editing an already-applied @ChangeUnit/@Change class doesn’t
fail loudly: the tool simply never re-runs it anywhere it already ran, silently and without error. Either way,
the fix is the same: never edit an applied change unit — append a new one that makes the additional change
instead (see Best Practices).
The Audit / Tracking Table
Each tool persists a durable record of what ran, when, by whom (as declared in the change unit), and its
checksum. Liquibase writes this to the DATABASECHANGELOG table inside the target database itself
(Liquibase: Tracking & Locking). Mongock
writes to an audit collection in the target MongoDB deployment. Flamingock generalizes this into a configurable
audit store, decoupled from any single target system, since a Flamingock pipeline may not even have a
database as one of its targets
(Flamingock: Changes & Target
Systems).
Distributed Locking
Before executing anything, each tool acquires a distributed lock so that two application instances starting up
concurrently — the common case in a horizontally scaled deployment — don’t both try to run the same pending
change units at once. Liquibase’s lock lives in the DATABASECHANGELOGLOCK table
(Liquibase: Tracking & Locking); Mongock
acquires a lock document in its own lock collection before a migration run
(Mongock: Transactions & Locking);
Flamingock does the same against its configured audit store. In every tool, a process that crashes while holding
the lock can leave it stuck, and each tool provides a way to release it manually.
Contexts / Environments
Sometimes a change unit should run in some environments and not others — a seed-data changeset for test but
never production, say. Liquibase supports this natively with contexts and labels
(Liquibase: Preconditions, Contexts &
Labels). Mongock and Flamingock achieve the same result at the application level — typically by conditionally
registering a change unit’s class, or by using Spring profiles — since their changelog is code rather than a
declarative file with a built-in filtering mechanism.
Forward-Only vs. Reversible Changes
A change unit can define only how to move forward, or it can also define its own inverse. Liquibase can often
auto-generate the inverse for simple, well-known change types (createTable → dropTable) and otherwise
requires an explicit <rollback> block
(Liquibase: Rollback). Mongock’s
@RollbackExecution and Flamingock’s @Rollback are both explicit methods the author writes by hand — there is
no automatic inverse for arbitrary code, and for a non-transactional target system (a message queue, a REST API)
a rollback method is often the only way to undo a partially-applied change
(Flamingock: Changes & Target
Systems).
Dry Run / Preview
Before actually applying anything, it is useful to see what would run. Liquibase’s updateSQL command prints
the SQL a real update would execute without running it. Mongock and Flamingock don’t expose an equivalent
preview command in the same way, since their change units are compiled code rather than declarative SQL/YAML — the closer equivalent there is reading the pending-change-unit list the tool computes at startup (visible in
its logs) before it executes anything, or testing a Change in isolation
(Flamingock: Testing).
Vocabulary at a Glance
| Concept | Liquibase | Mongock | Flamingock |
|---|---|---|---|
Atomic unit of work |
Changeset |
ChangeUnit |
Change |
Ordered list of units |
Changelog file(s) |
Classpath-scanned |
Classpath-scanned |
Tracking store |
|
Audit collection |
Configurable audit store |
Locking store |
|
Lock collection |
Configurable, via the audit store |
Per-environment filtering |
Contexts / labels |
Application-level (profiles, conditional registration) |
Application-level (profiles, conditional registration) |
Inverse operation |
Auto-generated or explicit |
Explicit |
Explicit |