This guide addresses common rollback problems encountered in Liquibase implementations, based on real customer support cases. Each section provides detailed explanations of the issue, root causes, and multiple solution approaches.
Table of Contents
- Checksum Errors During Rollback
- Rollback Order and Dependency Issues
- Using rollbackSqlFile for Complex Rollback Logic
Checksum Errors During Rollback
Problem: You receive a checksum validation error when attempting to rollback a changeset after editing your changelog file.
Example Error:
ERROR: Exception Details
ERROR: Exception Primary Class: ValidationFailedException
ERROR: Exception Primary Reason: Validation Failed:
1 changesets check sum
checksum-test.sql::1::john was: 9:0a5891c40b327d006708fc6660d02f8a but is now: 9:7fb597c6ca98a9a4b721e90504a838d3
For more information, please use the --log-level flag
Root Cause
Liquibase calculates and stores a checksum for each changeset when it's first deployed. This checksum is based on the changeset content and ensures the changeset hasn't been modified after deployment. When you edit a changeset that has already been deployed, the calculated checksum changes, causing validation to fail during rollback operations.
This safety mechanism prevents inconsistent database states that could occur if changesets were modified after deployment without proper tracking.
Solution Options
Option A: Use validCheckSum to accept changes (Recommended for minor edits)
If your changeset edits are safe, intentional, and do not need to be deployed, you can explicitly tell Liquibase to accept the new checksum:
SQL Format
--changeset john:1
--validCheckSum: 9:0a5891c40b327d006708fc6660d02f8a
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100)
);
--rollback DROP TABLE users;
XML Format
<changeSet id="1" author="john">
<validCheckSum>9:0a5891c40b327d006708fc6660d02f8a</validCheckSum>
<createTable tableName="users">
<column name="id" type="int">
<constraints primaryKey="true"/>
</column>
<column name="username" type="varchar(50)">
<constraints nullable="false"/>
</column>
<column name="email" type="varchar(100)"/>
</createTable>
</changeSet>
YAML Format
changeSet:
id: 1
author: john
validCheckSum: 9:0a5891c40b327d006708fc6660d02f8a
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: int
constraints:
primaryKey: true
- column:
name: username
type: varchar(50)
constraints:
nullable: false
- column:
name: email
type: varchar(100)
JSON Format
{
"changeSet": {
"id": "1",
"author": "john",
"validCheckSum": "9:0a5891c40b327d006708fc6660d02f8a",
"changes": [
{
"createTable": {
"tableName": "users",
"columns": [
{
"column": {
"name": "id",
"type": "int",
"constraints": {
"primaryKey": true
}
}
},
{
"column": {
"name": "username",
"type": "varchar(50)",
"constraints": {
"nullable": false
}
}
},
{
"column": {
"name": "email",
"type": "varchar(100)"
}
}
]
}
}
]
}
}
Option B: Revert and redeploy (Recommended for significant changes)
- Revert your changelog edits to the original deployed version
- Complete the rollback operation
- Create a new changeset with your desired changes
- Deploy the new changeset
Option C: runOnChange
Liquibase automatically reruns the changeset when it detects that the content has changed, as specified by runOnChange. This eliminates checksum validation errors, as the attribute expects modifications and maintains a single changeset entry, rather than creating new changesets for each modification.
Use runOnChange judiciously. It's designed for objects that naturally support replacement operations, not for structural changes that require careful rollback planning.
SQL Format
--changeset your.name:changeset01 runOnChange:true
CREATE or REPLACE . . .
XML Format
<changeSet author="your.name" id="changeset01" runOnChange="true" >
<createProcedure>
. . .
</createProcedure>
</changeSet>
YAML Format
changeset:
id: changeset01
author: your.name
runOnChange: true
changes:
- . . .
JSON Format
{
"changeSet":{
"id":"changeset01",
"author":"your.name",
"runOnChange":true,
"changes":[
{
". . ."
}
]
}
}Option D: Clear checksum validation (Use with caution)
Force Liquibase to recalculate checksums for all changesets:
liquibase clear-checksumsWarning: This approach bypasses all checksum validation and should only be used when you're certain about the integrity of your changesets.
Best Practices
- Avoid editing changesets after deployment when possible
- Create new changesets for changes instead of modifying existing ones
- Use
validCheckSumsparingly and document why it's needed
Rollback Order and Dependency Issues
Problem: When using rollback-count with multiple changesets, some rollback operations fail due to dependency issues, preventing subsequent rollbacks from executing.
Example Scenario:
You have three changesets deployed in this order:
-
changeset-1: Creates base table -
changeset-2: Creates a dependent table with a foreign key to the base table -
changeset-3: Adds index to dependent table
When you run liquibase rollback-count 3, Liquibase attempts to rollback in reverse order (3→2→1), but changeset-2's rollback fails because it incorrectly tries to drop the base table while the dependent table (with its foreign key constraint) still exists:
Example Error:
ERROR: Exception Details
ERROR: Exception Primary Class: PSQLException
ERROR: Exception Primary Reason: ERROR: cannot drop table base_table because other objects depend on it
Detail: constraint fk_base on table dependent_table depends on table base_table
Hint: Use DROP ... CASCADE to drop the dependent objects too.
Root Cause
Liquibase executes rollbacks in reverse chronological order (newest first), which can create dependency conflicts when:
- Rollback scripts are incomplete: Missing steps to handle database-generated constraints like default constraints, auto-generated indexes, or triggers
- Database-specific behavior: Some databases (like SQL Server) automatically create named constraints that must be explicitly dropped
- Cross-changeset dependencies: Later changesets create objects that depend on earlier changesets, but rollback scripts don't account for the proper cleanup order
Understanding Rollback Execution Order
Deployed order: changeset-1 → changeset-2 → changeset-3
Rollback order: changeset-3 → changeset-2 → changeset-1
Critical behavior: If changeset-2's rollback fails, Liquibase stops execution and does NOT continue to rollback changeset-1. This prevents potential database inconsistency but can leave your database in an intermediate state.
Solution Approaches
Option A: Fix the failing rollback (Recommended)
Identify and fix the specific rollback operation causing the failure, then retry the rollback command:
- Analyze the failure: Review the error message to understand which constraint or dependency is causing the issue
- Fix the rollback logic: Update the problematic changeset's rollback statement to handle dependencies correctly
-
Retry the rollback: Execute the same
rollback-countcommand again
Example Fix:
Problematic rollback script:
This changeset creates a dependent table with a foreign key, but the rollback incorrectly tries to drop the base table instead of cleaning up what it created:
--changeset your.name:2
CREATE TABLE dependent_table (
dep_id INT PRIMARY KEY,
base_id INT,
description VARCHAR(100)
);
ALTER TABLE dependent_table ADD CONSTRAINT fk_base FOREIGN KEY (base_id) REFERENCES base_table(id);
--rollback DROP TABLE base_table;
Fixed rollback script:
The corrected version properly cleans up the foreign key constraint first, then drops the dependent table it created:
--changeset your.name:2
CREATE TABLE dependent_table (
dep_id INT PRIMARY KEY,
base_id INT,
description VARCHAR(100)
);
ALTER TABLE dependent_table ADD CONSTRAINT fk_base FOREIGN KEY (base_id) REFERENCES base_table(id);
--rollback ALTER TABLE dependent_table DROP CONSTRAINT fk_base;
--rollback DROP TABLE dependent_table;
Option B: Preview with rollback-sql
Use rollback-sql to preview the exact operations and identify potential issues before executing:
# Preview what rollback-count 3 would do
liquibase rollback-sql-count 3
# Review the output for dependency issues before executing
liquibase rollback-count 3
Prevention Strategies
- Write dependency-aware rollbacks: Always consider what other changesets might depend on your changes
- Use strategic tagging: Create tags after deploying related groups of changesets
- Test rollback scenarios: Include rollback testing in your deployment pipeline
- Document dependencies: Clearly document when changesets have cross-dependencies
- Require rollbacks on all changesets: Enable policy checks (like RollbackRequired) to ensure that all changesets have an associated rollback
Using rollbackSqlFile for Complex Rollback Logic
Problem: Inline rollback statements become unwieldy for complex rollback scenarios, particularly when dealing with large stored procedures, complex data migrations, or extensive rollback logic.
Example Scenario: You need to create rollback logic for a stored procedure that spans 200+ lines, or you have complex data transformation logic that requires multiple steps and conditional operations.
When to Use rollbackSqlFile
Use rollbackSqlFile when:
- Rollback logic exceeds 10-15 lines
- You need complex conditional logic in rollback operations
- Working with large stored procedures or functions
- Rollback requires multiple discrete operations that benefit from organization
- You want to maintain rollback scripts separately for code organization
Continue using inline rollback when:
- Simple operations (single DROP, DELETE, or ALTER statement)
- Rollback logic is straightforward and brief
- The rollback is a simple inverse of the forward operation
Basic Syntax
Important: The syntax differs between SQL and modeled changelogs:
-
SQL changelogs use
--rollbackSqlFile -
Modeled changelogs (XML, YAML, JSON) use
sqlFilewithin the rollback element
SQL Format:
--changeset john:6
CREATE OR REPLACE FUNCTION large_stored_procedure() -- [complex logic here]
--rollbackSqlFile rollback/changeset-6-rollback.sqlXML Format:
<changeSet id="6" author="john">
<sql>CREATE OR REPLACE FUNCTION large_stored_procedure() -- [complex logic here]</sql>
<rollback>
<sqlFile path="rollback/changeset-6-rollback.sql"/>
</rollback>
</changeSet>YAML Format:
changeSet:
id: 6
author: john
changes:
- sql: CREATE OR REPLACE FUNCTION large_stored_procedure() -- [complex logic here]
rollback:
- sqlFile:
path: rollback/changeset-6-rollback.sqlJSON Format:
{
"changeSet": {
"id": "6",
"author": "john",
"changes": [
{"sql": "CREATE OR REPLACE FUNCTION large_stored_procedure() -- [complex logic here]"}
],
"rollback": [
{"sqlFile": {"path": "rollback/changeset-6-rollback.sql"}}
]
}
}
Comments
0 comments
Article is closed for comments.