Liquibase: Changelogs & Changesets

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.

Changeset Identity

A changeset is uniquely identified by the triple id + author + the changelog file it is declared in, per What is a Changeset?. Liquibase tracks this triple in DATABASECHANGELOG (Liquibase: Tracking & Locking) — change any part of it and Liquibase treats the changeset as new and runs it again, even if the SQL inside is identical to one already applied. id does not need to be numeric or sequential; a descriptive string (add-email-column) works just as well and is often clearer in review.

One Change Type per Changeset

Liquibase recommends keeping exactly one change type (createTable, addColumn, addForeignKeyConstraint, and so on) per changeset. This matters most for rollback: Liquibase’s auto-generated inverse (Liquibase: Rollback) is derived per change type, and a changeset mixing several change types either can’t be auto-rolled-back at all or rolls back in a way that’s harder to reason about. It also keeps the audit trail’s granularity meaningful — one row in DATABASECHANGELOG per logical change.

Splitting Large Changelogs: include and includeAll

A single master changelog can pull in others, so a large schema’s history doesn’t live in one file:

databaseChangeLog:
  - include:
      file: db/changelog/001-initial-schema.yaml
  - include:
      file: db/changelog/002-add-email-column.yaml
  - includeAll:
      path: db/changelog/releases/

include names one file explicitly; includeAll pulls in every changelog file under a directory, in lexicographic filename order — the common convention is to prefix each file with a date or an incrementing number so that ordering is obvious from the filename alone. Splitting by feature, by release, or by team keeps merge conflicts down when several people add changesets concurrently.

A Two-Changeset Example: Ordering Across Files

Changeset 1 (001-create-person-table.yaml) creates a table:

databaseChangeLog:
  - changeSet:
      id: create-person-table
      author: alberto
      changes:
        - createTable:
            tableName: person
            columns:
              - column: { name: id, type: bigint, autoIncrement: true, constraints: { primaryKey: true } }
              - column: { name: name, type: varchar(255), constraints: { nullable: false } }

Changeset 2 (002-add-person-email.yaml), included after it, adds a column:

databaseChangeLog:
  - changeSet:
      id: add-person-email
      author: alberto
      changes:
        - addColumn:
            tableName: person
            columns:
              - column: { name: email, type: varchar(255) }

Because both files are pulled in via includeAll from a directory where 001- sorts before 002-, Liquibase applies create-person-table before add-person-email on a fresh database — but on a database that already has create-person-table recorded as applied, only add-person-email runs.

Generating a Changelog from an Existing Database

For a database that predates Liquibase, generateChangeLog inspects the live schema and writes out a changelog that would recreate it — a starting point for bringing an existing database under Liquibase management:

liquibase generateChangeLog --changelog-file=baseline.yaml

diff and diffChangeLog instead compare two databases (or a database against a changelog) and produce only the changesets needed to reconcile the difference — useful for catching drift, or for generating the changesets for a change that was prototyped directly against a database:

liquibase diffChangeLog --reference-url=jdbc:postgresql://localhost/reference --changelog-file=diff.yaml

Both commands produce a starting draft, not a finished changelog — review the output before committing it, since generated changesets don’t include the semantic intent (why the schema looks the way it does) a hand-written one would.