Когда сервис изначально захардкодил 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_dbDATABASECHANGELOG
При переносе данных в новую БД обязательно копировать таблицы
DATABASECHANGELOGиDATABASECHANGELOGLOCK. Без них Liquibase считает все changeset’ы непримёненными и пытается выполнить их заново.
Порядок элементов в changeSet
validCheckSum ставится первым дочерним элементом, перед preConditions и comment:
<changeSet id="..." author="...">
<validCheckSum>ANY</validCheckSum>
<preConditions onFail="CONTINUE">...</preConditions>
<comment>...</comment>
...
</changeSet>