Final StretchOrdered learning track

Liquibase Production Workflow

Learn Java Data Access Pattern In Action - Part 055

Liquibase production workflow untuk Java: changelog, changeset, idempotency, checksum, DATABASECHANGELOG, precondition, rollback, label, context, include, formatted SQL, XML/YAML/JSON, generateChangeLog, diff, documentation, dan multi-env migration safety.

12 min read2312 words
PrevNext
Lesson 5560 lesson track51–60 Final Stretch
#java#data-access#liquibase#database-migration+6 more

Part 055 — Liquibase Production Workflow

Liquibase adalah database migration tool yang lebih declarative dan metadata-rich dibanding migration SQL sederhana.

Konsep utamanya:

changelog -> changeset -> DATABASECHANGELOG -> checksum -> preconditions -> rollback -> labels/contexts

Liquibase kuat untuk environment kompleks, compliance, generated documentation, multi-database support, rollback metadata, dan precondition.

Tetapi sama seperti Flyway, Liquibase tidak otomatis membuat schema change menjadi zero-downtime. Compatibility tetap tanggung jawab engineer.

Part ini membahas workflow Liquibase untuk production Java.


1. Core Thesis

Liquibase production workflow harus membuat perubahan database:

versioned,
auditable,
validated,
environment-aware,
reversible where realistic,
compatible with rolling deploy,
and observable.

Liquibase memberi:

  • changelog terstruktur;
  • changeset dengan author/id;
  • checksum;
  • preconditions;
  • rollback definition;
  • labels and contexts;
  • include/includeAll;
  • generateChangeLog/diff;
  • formatted SQL;
  • XML/YAML/JSON changelog;
  • documentation generation;
  • database-agnostic abstractions.

Tetapi engineer tetap harus mendesain:

  • expand-contract;
  • backfill strategy;
  • lock avoidance;
  • online index;
  • data validation;
  • app compatibility;
  • operational runbook.

2. Liquibase Core Concepts

Core objects:

changelog
  contains changesets

changeset
  unique by id + author + file path
  contains changes

DATABASECHANGELOG
  records executed changesets

DATABASECHANGELOGLOCK
  prevents concurrent execution

A changeset is not just a file. Its identity is:

id + author + changelog path

This matters when moving files.


3. Changelog Example

XML:

<databaseChangeLog
    xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.liquibase.org/xml/ns/dbchangelog
        http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">

    <changeSet id="20260705-001-add-case-priority" author="case-platform">
        <addColumn tableName="case_file">
            <column name="priority" type="varchar(32)"/>
        </addColumn>
    </changeSet>

</databaseChangeLog>

YAML/JSON/SQL also possible.


4. Formatted SQL

Liquibase formatted SQL:

--liquibase formatted sql

--changeset case-platform:20260705-001-add-case-priority
alter table case_file
add column priority varchar(32);

--rollback alter table case_file drop column priority;

Formatted SQL is popular for teams that want hand-written SQL while using Liquibase metadata.

It is often the most reviewable option for DB-specific production SQL.


5. XML/YAML/JSON vs SQL

FormatStrength
XML/YAML/JSONdeclarative, portable, tool-readable
formatted SQLexact SQL control, easy DB review
raw SQL includedflexible
Java/custom changeprocedural logic

Production recommendation:

Use formatted SQL or SQL for DB-specific critical DDL.
Use XML/YAML for portable simple changes if team likes it.
Do not sacrifice SQL clarity for false portability.

6. Changeset Identity

Changeset identity:

author + id + file path

If you move changelog file, Liquibase may consider changeset new unless logical file path used.

Use logicalFilePath when restructuring changelogs.

Example:

<databaseChangeLog logicalFilePath="db/changelog/case-platform.xml">

Be careful refactoring migration file structure after production use.


7. DATABASECHANGELOG

Liquibase records execution in DATABASECHANGELOG.

It includes:

  • id;
  • author;
  • filename;
  • date executed;
  • order executed;
  • exec type;
  • md5sum/checksum;
  • description;
  • comments;
  • tag;
  • Liquibase version;
  • contexts/labels.

This table is audit evidence.


8. DATABASECHANGELOGLOCK

Liquibase uses lock table to prevent concurrent migration.

If migration process crashes, lock may remain.

Runbook:

  1. verify no Liquibase process still running;
  2. inspect lock table;
  3. release lock using Liquibase command or SQL if documented;
  4. rerun validate/update.

Do not delete locks blindly while migration may still be running.


9. Checksum

Liquibase stores checksum for changeset.

If changeset content changes after execution, validation may fail.

Rule:

Do not edit executed changeset in shared/prod environments.
Create new changeset.

Liquibase has validCheckSum escape hatch, but use sparingly and document.


10. runOnChange

runOnChange="true" reruns changeset when checksum changes.

Example use:

  • view definition;
  • stored procedure;
  • function;
  • generated docs object.
<changeSet id="case-dashboard-view" author="case-platform" runOnChange="true">
    <createView viewName="case_dashboard_view" replaceIfExists="true">
        select ...
    </createView>
</changeSet>

Caution:

  • changing view/procedure can break old app during rolling deploy;
  • use versioned DB object names for incompatible changes.

11. runAlways

runAlways="true" runs every time Liquibase runs.

Use rarely.

Examples:

  • update audit metadata;
  • refresh non-critical object;
  • grant statement in some environments.

Danger:

  • non-idempotent changes;
  • unexpected production side effects;
  • slow repeated migrations.

Prefer explicit versioned changesets.


12. Preconditions

Preconditions guard changes.

Example:

<changeSet id="20260705-002-add-priority-not-null" author="case-platform">
    <preConditions onFail="HALT">
        <sqlCheck expectedResult="0">
            select count(*) from case_file where priority is null
        </sqlCheck>
    </preConditions>

    <addNotNullConstraint
        tableName="case_file"
        columnName="priority"
        columnDataType="varchar(32)"/>
</changeSet>

This prevents adding not-null before backfill complete.


13. Preconditions onFail

Common onFail options conceptually include:

  • HALT: stop migration;
  • MARK_RAN: mark as executed without running;
  • WARN: warn and continue;
  • CONTINUE: skip/continue depending context.

Production default should usually be HALT for safety-critical preconditions.

MARK_RAN can be useful for idempotent compatibility but dangerous if hiding missing schema.


14. Preconditions for Existing Object

<preConditions onFail="MARK_RAN">
    <not>
        <columnExists tableName="case_file" columnName="priority"/>
    </not>
</preConditions>

Then add column.

This can help when environments drift slightly.

But if drift is unexpected, MARK_RAN may hide real problem.

Use only with known adoption/baseline scenario.


15. Rollback Definition

Liquibase lets you define rollback.

Formatted SQL:

--changeset case-platform:20260705-001-add-priority
alter table case_file add column priority varchar(32);

--rollback alter table case_file drop column priority;

XML:

<rollback>
    <dropColumn tableName="case_file" columnName="priority"/>
</rollback>

Rollback metadata is useful, but production rollback may still be unsafe if data written.


16. Rollback Reality

Rollback can be:

  • safe for adding unused nullable column;
  • unsafe for dropping column after data written;
  • impossible for destructive data transformation;
  • misleading if app already deployed.

Always distinguish:

Liquibase rollback command exists

from:

Production rollback is operationally safe

For many production changes, forward fix is safer.


17. Rollback Testing

Test rollback in non-prod if you rely on it.

But be careful: successful rollback on empty DB does not prove safe rollback after real traffic writes data.

Rollback story should include:

  • app rollback compatibility;
  • data written in new schema;
  • feature flags;
  • backup/restore;
  • forward fix.

18. Labels

Labels select changesets by label expression.

Example:

<changeSet id="20260705-010-add-dashboard-index" author="case-platform" labels="dashboard,performance">
    ...
</changeSet>

Run:

liquibase update --label-filter=dashboard

Use labels for controlled deployment groups if needed.

Caution:

  • selective migration can create environment divergence;
  • label strategy must be documented.

Most core schema changes should run everywhere.


19. Contexts

Contexts are often used for environment-specific changes.

Example:

<changeSet id="dev-seed-case-data" author="dev" context="dev,test">
    ...
</changeSet>

Use for:

  • local/test seed data;
  • dev-only helpers;
  • test fixtures maybe.

Avoid using contexts for core production schema differences unless absolutely needed.

Production schema should be consistent.


20. Include and IncludeAll

Root changelog:

<databaseChangeLog ...>
    <include file="db/changelog/001-case-file.xml"/>
    <include file="db/changelog/002-case-priority.xml"/>
</databaseChangeLog>

includeAll includes directory contents.

Caution:

  • ordering depends file name sorting;
  • accidental file inclusion;
  • path changes affect changeset identity unless logical path.

For production, explicit include gives more control.


21. Changelog Organization

Possible structure:

db/changelog/
  db.changelog-master.xml
  releases/
    2026-07/
      20260705-001-add-case-priority.sql
      20260705-002-add-dashboard-index.sql
  repeatable/
    case-dashboard-view.sql
  dev/
    seed-dev-data.sql

Keep:

  • production changes separate from dev/test seed;
  • ordering explicit;
  • names descriptive.

22. Generated Changelog

Liquibase can generate changelog from existing DB.

Use cases:

  • initial baseline;
  • documentation;
  • diff review.

Caution:

  • generated changelog may include noisy/vendor-specific details;
  • object order may need cleanup;
  • constraints/index names may be ugly;
  • not necessarily production-ready.

Review generated changelog manually before using.


23. Diff

Liquibase diff compares databases/snapshots.

Use cases:

  • drift detection;
  • compare expected vs actual;
  • generate migration draft.

Caution:

  • diff-generated SQL/changelog must be reviewed;
  • may include unintended changes;
  • can miss semantic intent;
  • should not replace human migration design.

24. UpdateSQL

Liquibase can output SQL without applying.

Use:

updateSQL

Benefits:

  • review generated SQL;
  • DBA review;
  • preview operations;
  • detect unexpected DDL.

Still need real apply test.


25. ChangelogSync

Changelog sync marks changesets as executed without running.

Use for baseline/adoption scenarios.

Dangerous in production if used incorrectly.

Only use when DB schema already contains equivalent changes and you have verified it.


26. Tagging

Liquibase supports tagging database state.

Use:

tag release-2026-07-05

Useful for audit/release mapping.

But tag does not make rollback safe by itself.


27. Liquibase Lock Runbook

If locked:

  1. run status/inspect lock;
  2. ensure no active migration;
  3. check deployment logs;
  4. release lock using Liquibase command;
  5. validate;
  6. rerun update if safe.

Do not release lock while another migration is still executing.


28. Large Backfill With Liquibase

Avoid huge backfill inside changeset.

Bad:

update case_file set priority = 'NORMAL' where priority is null;

on 500M rows inside one migration.

Better:

  1. Liquibase adds nullable column.
  2. Backfill job chunks data with progress.
  3. Liquibase precondition verifies no null.
  4. Liquibase adds not-null/check constraint.

Liquibase manages schema. Operational job manages large data movement.


29. Online Index With Liquibase

Use raw SQL if database-specific.

Formatted SQL:

--liquibase formatted sql

--changeset case-platform:20260705-003-create-dashboard-index runInTransaction:false
create index concurrently if not exists ix_case_file_tenant_status_updated
on case_file(tenant_id, status, updated_at desc, id desc);

--rollback drop index concurrently if exists ix_case_file_tenant_status_updated;

runInTransaction:false may be needed for PostgreSQL concurrent index.

Verify syntax supported by your Liquibase version.


30. Lock Timeout in Changeset

--changeset case-platform:20260705-004-add-priority-check
set lock_timeout = '5s';
alter table case_file
add constraint ck_case_file_priority
check (priority in ('LOW', 'NORMAL', 'HIGH'));

Be careful with session settings and transaction boundaries.

If changeset contains multiple statements, timeout applies according to DB semantics.


31. Constraint With Preconditions

<changeSet id="20260705-005-add-priority-check" author="case-platform">
    <preConditions onFail="HALT">
        <sqlCheck expectedResult="0">
            select count(*)
            from case_file
            where priority is null
               or priority not in ('LOW', 'NORMAL', 'HIGH')
        </sqlCheck>
    </preConditions>

    <addCheckConstraint
        tableName="case_file"
        constraintName="ck_case_file_priority"
        checkConstraint="priority in ('LOW', 'NORMAL', 'HIGH')"/>
</changeSet>

If DB/tool support for addCheckConstraint varies, raw SQL may be clearer.


32. Baseline Existing DB

For existing production DB:

  1. generate/document current schema;
  2. create baseline tag/version;
  3. run changelogSync or baseline equivalent;
  4. start future changesets after baseline;
  5. ensure new environments can be built from baseline + migrations.

Be explicit about baseline version and schema state.


33. Multi-Database Support

Liquibase can target multiple DBs with abstractions/preconditions.

But production multi-DB support is expensive.

If app truly supports multiple DB vendors:

  • test migrations on all vendors;
  • avoid raw vendor SQL or gate it;
  • use DB-specific changesets with dbms;
  • understand feature differences.

If production uses one DB, prioritize exact SQL safety over theoretical portability.


34. dbms Attribute

Example:

<changeSet id="20260705-010-pg-index" author="case-platform" dbms="postgresql">
    <sql>
        create index concurrently ...
    </sql>
</changeSet>

Use to isolate vendor-specific changes.

But avoid leaving other DBs without required equivalent migration.


35. Environment Safety

Production Liquibase config should have:

  • correct changelog;
  • clean/drop disabled by policy;
  • contexts/labels explicit;
  • migration user credentials separated;
  • lock wait policy;
  • logs captured;
  • rollback command restricted;
  • updateSQL reviewed for risky changes.

36. Spring Boot Integration

Spring Boot can run Liquibase at startup.

Example config concept:

spring:
  liquibase:
    enabled: true
    change-log: classpath:db/changelog/db.changelog-master.xml

Startup migration is okay for simple systems.

For risky DDL/large environments, dedicated migration job is safer.


37. Dedicated Migration Job

Production deployment pattern:

build artifact
run liquibase update as migration job
deploy app

Pros:

  • one runner;
  • clear logs;
  • migration user separate;
  • controlled timing;
  • easier approval.

Cons:

  • pipeline complexity;
  • app/schema coordination needed.

For mature production, dedicated migration stage is recommended.


38. Liquibase Hub / Reports / Documentation

Liquibase can generate reports/documentation depending edition/tooling.

Even without advanced product features, you can document:

  • changelog;
  • changesets;
  • deployment logs;
  • tags;
  • SQL output;
  • schema diff.

For compliance, retain migration evidence.


39. Compliance Evidence

For regulated environments, preserve:

  • changelog version;
  • commit SHA;
  • migration runner;
  • execution timestamp;
  • database target;
  • updateSQL artifact if reviewed;
  • approval record;
  • DATABASECHANGELOG snapshot;
  • rollback/forward-fix plan.

Migration is auditable change.


40. Liquibase and jOOQ

Pipeline:

Liquibase update test DB
jOOQ codegen
compile
integration tests

If Liquibase changelog changes schema, generated jOOQ code catches code mismatch.

This is strong workflow.


41. Liquibase and JPA

Do not rely on Hibernate auto DDL in production when using Liquibase.

Production:

Liquibase owns schema.
JPA validates or maps schema.

Use Hibernate validate/none depending setup.

Avoid two tools modifying schema independently.


42. Liquibase and MyBatis/JDBC

String SQL requires real DB integration tests.

Liquibase can build schema for tests, then MyBatis/JDBC tests run against it.

This catches migration/query drift.


43. Rollback Command Governance

Restrict who can run rollback in production.

Rollback may drop data.

Require:

  • approval;
  • impact analysis;
  • backup check;
  • app compatibility;
  • tested rollback;
  • forward-fix alternative considered.

Do not make rollback button easy for destructive migrations.


44. Handling Failed Changeset

If changeset fails:

  1. Liquibase stops.
  2. DATABASECHANGELOG may not mark success.
  3. Partial DDL may exist depending DB transaction.
  4. Lock may remain if abnormal crash.
  5. Next run may fail due partial object.

Runbook:

  • inspect actual schema;
  • cleanup partial changes;
  • release lock if safe;
  • create fix changeset if shared/prod;
  • rerun update;
  • document.

45. ValidCheckSum

validCheckSum can allow checksum variations.

Use cases:

  • line-ending/comment-only correction;
  • emergency repair where content equivalent;
  • controlled legacy migration import.

Danger:

  • hides edited migration.

Use sparingly with comment explaining why.


46. Idempotent SQL

Some SQL can be idempotent:

create table if not exists ...
create index if not exists ...

This helps rerun safety.

But idempotent SQL can also hide drift:

table exists but wrong definition

For core schema, prefer exact migration + validation/preconditions.


47. Changelog Review Checklist

  • Changeset id unique and descriptive.
  • Author/team meaningful.
  • Existing executed changeset not edited.
  • Preconditions used for risky state assumptions.
  • Rollback defined or forward-fix story documented.
  • Labels/contexts not causing prod divergence.
  • Raw SQL reviewed.
  • runOnChange only for compatible objects.
  • Large backfill not one giant changeset.
  • Online index transaction mode correct.
  • Constraint names explicit.
  • DB-specific SQL gated or intended.
  • UpdateSQL reviewed for risky changes.
  • Real DB migration test passes.
  • App compatibility considered.

48. Liquibase Config Checklist

  • Master changelog explicit.
  • Contexts/labels controlled per environment.
  • Migration user separate from app user if possible.
  • Lock table monitored.
  • Rollback command restricted.
  • Startup vs dedicated job decision documented.
  • Logs retained.
  • CI runs update + validate.
  • Previous release upgrade tested.
  • Drift detection process exists.
  • Production config cannot run destructive commands accidentally.

49. Anti-Pattern: Complex Business Backfill in Startup Liquibase

Can block app and hide operational risk.

Use separate job.


50. Anti-Pattern: MARK_RAN to Ignore Problems

Can hide missing schema.

Use only for known baseline/adoption compatibility.


51. Anti-Pattern: Rollback Metadata as Safety Guarantee

Rollback command exists does not mean rollback is safe.


52. Anti-Pattern: Contexts Creating Different Production Schemas

Keep production schema consistent unless architecture explicitly requires difference.


53. Anti-Pattern: Editing Changeset With validCheckSum Band-Aid

Create new changeset.


54. Mini Lab

Design Liquibase changelog for:

Add case priority:
- add nullable priority column;
- add check constraint after backfill;
- add concurrent dashboard index;
- update dashboard view;
- seed priority reason codes;
- support old app during rolling deploy.

Tasks:

  1. Choose changelog format.
  2. Define changeset ids/authors.
  3. Add precondition before check constraint.
  4. Decide which step is external backfill job.
  5. Decide rollback blocks.
  6. Use runInTransaction:false where needed.
  7. Use labels/contexts or avoid them.
  8. Generate updateSQL for DBA review.
  9. Define CI test.
  10. Define production runbook.

55. Summary

Liquibase is powerful for structured, auditable database change management.

You must master:

  • changelog;
  • changeset identity;
  • DATABASECHANGELOG;
  • DATABASECHANGELOGLOCK;
  • checksum;
  • formatted SQL;
  • XML/YAML/JSON trade-offs;
  • runOnChange/runAlways;
  • preconditions;
  • rollback reality;
  • labels/contexts;
  • include/includeAll;
  • generateChangeLog/diff/updateSQL;
  • changelogSync/baseline;
  • online index with non-transaction changeset;
  • large backfill separation;
  • multi-db/dbms support;
  • Spring Boot integration;
  • dedicated migration job;
  • compliance evidence;
  • failed changeset runbook;
  • validCheckSum caution;
  • review/config checklists.

Part berikutnya membahas Expand-Contract Database Change secara mendalam: backward-compatible schema change, dual write, backfill, read switch, cleanup, feature flags, and production rollout choreography.


56. References

Lesson Recap

You just completed lesson 55 in final stretch. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.