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.

sequenceDiagram participant A as Instance A (startup) participant B as Instance B (startup) participant Lock as Distributed lock participant Audit as Audit / tracking table par concurrent startup A->>Lock: acquire lock B->>Lock: acquire lock (blocks) end Lock-->>A: lock granted A->>Audit: read applied change units, execute pending ones A->>Lock: release lock Lock-->>B: lock granted B->>Audit: read applied change units Note over B: nothing pending -- A already ran them B->>Lock: release lock

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 (createTabledropTable) 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 @ChangeUnit classes

Classpath-scanned @Change classes, grouped into stages

Tracking store

DATABASECHANGELOG table

Audit collection

Configurable audit store

Locking store

DATABASECHANGELOGLOCK table

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 <rollback>

Explicit @RollbackExecution

Explicit @Rollback (optional if transactional, mandatory otherwise)