All About Changeset Checksums

Originally published at: All About Changeset Checksums

Updated October 8, 2021

In order to detect changes between what is currently in the changelog vs. what was actually run against the database, Liquibase stores a checksum with each changeset entry in the DATABASECHANGELOG tracking table.

Liquibase uses a checksum to detect if your target database was updated. The checksum is computed after the parameters are applied. For example, let’s say your target database already ran the following changeset:

<changeSet id="1" author="example">
   <addColumn tableName="my_table">
       <column name="my_column" type="INT"/>
   </addColumn>
</changeSet>

This changeset adds “my_column” as an “int”. The changelog contains the same “id=1, author=example” changeset as the following changeset:

<changeSet id="1" author="example">
   <addColumn tableName="my_table">
       <column name="my_column" type="BIGINT"/>
   </addColumn>
</changeSet>

This changeset adds “my_column” as a “bigint” instead.

Since Liquibase tracks which changesets have run against your target database by the id/author/filepath combination, it sees the updated changeset as “ran” even though your target database has “my_column” as an “int” column rather than the expected “bigint” type.

If you run the “int” changeset against your database and then change it to “bigint” and re-run update, Liquibase will exit with the following error:

Validation Failed:
 1 change sets check sum
com/example/changelog.xml::1::example was: 8:63f82d4ff1b9dfa113739b7f362bd37d but is now: 8:b4fd16a20425fe377b00d81df722d604

This error shows how Liquibase notices that your target database is out of sync with the changelog.

However, not every change to a changeset impacts the checksum. Here are some examples:

  • Reformatting whitespace and linebreaks (except within SQL statements)
  • Changing preconditions
  • Changing contexts
  • Changing labels
  • Adding validCheckSum settings
  • Changing comments (via native xml/yaml comments or using the comment tag)

Troubleshooting checksums

Don’t fear the checksum. It is here to help you add metadata to changesets that have already been run without problems. Checksums are also helpful because they can catch errors early in your pipeline and revert the changeset back to the original version and help you roll forward with a new changeset.

If you do end up with a checksum issue, you have several options to address it. 

Revert and roll forward

Using our earlier example above, you can set the type back to “int” and add a new modifyDataType changeset to change the column from “int” to “bigint”. Previous development databases would need to be rebuilt with the correct checksum, but the fact that they didn’t catch the checksum error originally means they are ephemeral and rebuilding is the easier option.

Valid checksum tag

If the revert + roll-forward approach doesn’t work for you, you can use the <validCheckSum> tag to specify which checksum that you want to accept as valid, even if it’s different than what was stored in the DATABASECHANGELOG table. Use the checksum values from the error message.

Using our example, your final changeset would look like the following:

<changeSet id="1" author="example">
   <validCheckSum>8:b4fd16a20425fe377b00d81df722d604</validCheckSum>
   <addColumn tableName="my_table">
       <column name="my_column" type="BIGINT"/>
   </addColumn>
</changeSet>

The type is still “bigint”, but it now has a validCheckSum tag. 

Running this changeset against your database which originally ran the “int” version will no longer complain about the checksum error — but it still considers the changeset as “ran”. That means the column will remain an “int”. 

If that is okay with you, you can leave it that way. If it’s not okay and you want to update it, you can add an additional modifyDataType changeset from “int” to “bigint” with a “changeSetRan” onFail=MARK_RAN precondition right before the addColumn changest. Doing this will migrate only databases that had already run the changest and leave the rest. 

<changeSet id="int-fixup" author="example">
   <preConditions onFail="MARK_RAN">
       <changeSetExecuted id="1" author="example"/>
   </preConditions>
   <modifyDataType tableName="my_table" columnName="my_column" newDataType="BIGINT"/>
</changeSet>

<changeSet id=“1” author=“example”>
<validCheckSum>8:b4fd16a20425fe377b00d81df722d604</validCheckSum>
<addColumn tableName=“my_table”>
<column name=“my_column” type=“BIGINT”/>
</addColumn>
</changeSet>

Manually modify

Of course, if it is just a handful of “problem” databases, you can also just manually modify their datatype and be done.