Когда сервис изначально захардкодил schemaName в Liquibase changeset’ах, а позже нужно переименовать схему или сделать её имя конфигурируемым — возникает каскад проблем с чексуммами и raw SQL.

Проблема

Liquibase считает чексумму каждого changeset’а. Если changeset уже выполнен и записан в DATABASECHANGELOG, любое изменение XML (включая удаление атрибута schemaName) приводит к несовпадению чексумм. При следующем запуске Liquibase падает с Validation Failed: checksum mismatch.

При этом хардкод schemaName в XML-атрибутах — не единственная проблема. Имя схемы часто проникает в:

  • raw SQL внутри <sql> тегов (UPDATE old_schema.table SET ...)
  • тело <createView> (SELECT ... FROM old_schema.table)

Liquibase подставляет defaultSchemaName только в XML-атрибуты (tableName, schemaName), но не в тело SQL.

Решение

Три изменения решают проблему для всех сценариев: новые инсталляции, существующие стенды, пропуск версий при обновлении.

1. Параметр для имени схемы

Имя схемы передаётся в changelog через change-log-parameters в application.yml:

quarkus:
  liquibase:
    default-schema-name: ${DB_SCHEMA:my_schema_name}
    change-log-parameters:
      schema: ${DB_SCHEMA:my_schema_name}

После этого ${schema} доступен в любом месте changelog, включая тело SQL:

<!-- было -->
<sql>UPDATE old_schema_name.my_table SET ...</sql>
 
<!-- стало -->
<sql>UPDATE ${schema}.my_table SET ...</sql>

Аналогично для VIEW:

<createView viewName="my_view">
    SELECT * FROM ${schema}.my_table
</createView>

Не использовать <property> с ${liquibase.defaultSchemaName}

<property name="schema" value="${liquibase.defaultSchemaName}"/> в changelog.xml не работает в Quarkus Liquibase extension — liquibase.defaultSchemaName не доступен как Liquibase property и передаётся в SQL как литеральная строка ${liquibase.defaultSchemaName}. Используйте change-log-parameters в application.yml.

2. validCheckSum для обратной совместимости

Во все changeset’ы, где изменился XML (удалён schemaName, заменён raw SQL), добавляется:

<changeSet id="..." author="...">
    <validCheckSum>ANY</validCheckSum>
    ...
</changeSet>

ANY говорит Liquibase принять любую ранее сохранённую чексумму. Changeset не перевыполняется — он уже отмечен как выполненный в DATABASECHANGELOG.

Почему ANY, а не конкретная чексумма

Конкретную чексумму нужно получать из DATABASECHANGELOG каждого стенда или вычислять через liquibase calculateCheckSum. При большом количестве changeset’ов и стендов это нереалистично. ANY безопасен, потому что эти changeset’ы уже иммутабельны по смыслу.

3. Конфигурируемое имя схемы

В application.yml имя схемы выносится в переменную окружения с дефолтом:

quarkus:
  liquibase:
    default-schema-name: ${DB_SCHEMA:new_default_schema_name}
    change-log-parameters:
      schema: ${DB_SCHEMA:new_default_schema_name}

Сценарии обновления

СценарийЧто происходит
Новая инсталляцияDATABASECHANGELOG пуст, changeset’ы выполняются с defaultSchemaName из конфига, validCheckSum не играет роли
Существующий стенд, та же БДvalidCheckSum=ANY принимает старые чексуммы, changeset’ы не перевыполняются, новые используют defaultSchemaName
Существующий стенд, новая БДDevOps переносит данные вместе с DATABASECHANGELOG (pg_dump/pg_restore). После — аналогично предыдущему
Пропуск версий (v1 → v5)Старые changeset’ы пропускаются (validCheckSum), новые выполняются с defaultSchemaName

Переименование схемы у существующего заказчика

Liquibase не может переименовать схему самостоятельно — он ищет DATABASECHANGELOG в defaultSchemaName ещё до выполнения changeset’ов. Если схема ещё не переименована, таблица не будет найдена.

Два варианта:

ALTER SCHEMA (если есть права)

ALTER SCHEMA old_schema_name RENAME TO new_schema_name;

Выполняется DevOps до старта приложения. Мгновенная операция, данные не копируются.

Новая БД (если ALTER невозможен)

pg_dump -n old_schema_name old_db | \
sed 's/old_schema_name/new_schema_name/g' | \
psql new_db

DATABASECHANGELOG

При переносе данных в новую БД обязательно копировать таблицы DATABASECHANGELOG и DATABASECHANGELOGLOCK. Без них Liquibase считает все changeset’ы непримёненными и пытается выполнить их заново.

Порядок элементов в changeSet

validCheckSum ставится первым дочерним элементом, перед preConditions и comment:

<changeSet id="..." author="...">
    <validCheckSum>ANY</validCheckSum>
    <preConditions onFail="CONTINUE">...</preConditions>
    <comment>...</comment>
    ...
</changeSet>