Миграции схемы данных YDB с Flyway и распределенные блокировки
Привет, Хабр! Меня зовут Кирилл Курдюков, я разработчик YDB. Ранее мы рассматривали интеграцию YDB c Liquibase, теперь поговорим о результатах поддержки инструмента для управления миграциями схемы данных YDB.
Введение
Flyway — это инструмент для миграций схем баз данных с открытым исходным кодом. Он имеет расширения для различных систем управления базами данных (СУБД), включая YDB. Является таким же широко используемым и популярным инструментом для управления миграциями схем базы данных, как и Liquibase.
Flyway и Liquibase определяют, какие изменения уже были применены к базе данных, а какие еще не были. Для этого они ведут журнал выполненных миграций в самой базе данных.
Они используют распределенную блокировку для обеспечения последовательности исполнения и целостности данных. Это важно, когда множество экземпляров приложения работает одновременно и может попытаться выполнить новые миграции параллельно. При отсутствии блокировки такая ситуация могла бы привести к несогласованности в базе данных.
Распределенная блокировка гарантирует, что только одна миграция может быть выполнена в любой момент времени.
Как использовать Flyway вместе с YDB
Чтобы использовать Flyway вместе с YDB в Java / Kotlin приложении или в Gradle / Maven плагинах, требуется установить зависимость расширения Flyway для YDB и YDB JDBC Driver:
Maven:
org.flywaydb
flyway-core
${flyway.core.version}
tech.ydb.jdbc
ydb-jdbc-driver
${ydb.jdbc.version}
tech.ydb.dialects
flyway-ydb-dialect
${flyway.ydb.dialect.version}
Gradle:
dependencies {
// Set actual versions
implementation "org.flywaydb:flyway-core:$flywayCoreVersion"
implementation "tech.ydb.dialects:flyway-ydb-dialect:$flywayYdbDialecVersion"
implementation "tech.ydb.jdbc:ydb-jdbc-driver:$ydbJdbcVersion"
}
Для работы с YDB через Flyway CLI требуется установить утилиту flyway
любым из рекомендованных способов.
Затем Flyway нужно расширить диалектом YDB и JDBC-драйвером:
# install flyway
# cd $(which flyway) // prepare this command for your environment
cd libexec
# set actual versions .jar files
cd drivers && curl -L -o ydb-jdbc-driver-shaded-2.1.0.jar https://repo.maven.apache.org/maven2/tech/ydb/jdbc/ydb-jdbc-driver-shaded/2.1.0/ydb-jdbc-driver-shaded-2.1.0.jar
cd ../lib && curl -L -o flyway-ydb-dialect.jar https://repo.maven.apache.org/maven2/tech/ydb/dialects/flyway-ydb-dialect/1.0.0-RC0/flyway-ydb-dialect-1.0.0-RC0.jar
Управление миграциями с помощью Flyway
baseline
Команда baseline инициализирует Flyway в существующей базе данных, исключая все миграции вплоть до baselineVersion
включительно.
Предположим, что мы имеем существующий проект с текущей схемой базы данных:
Запишем наши существующие миграции следующим образом (таблицы взяты из примера):
db/migration:
V1__create_series.sql
V2__create_seasons.sql
V3__create_episodes.sql
Установим baselineVersion = 3
, затем выполним следующую команду:
flyway -url=jdbc:ydb:grpc://localhost:2136/local -locations=db/migration -baselineVersion=3 baseline
Результатом исполнения будет созданная таблица flyway_schema_history
с baseline
записью:
Создана baseline
запись
migrate
Команда migrate обновляет схему базы данных до последней версии. Если таблица истории схемы не была создана, то Flyway создаст ее автоматически.
Добавим к предыдущему примеру миграцию загрузки данных:
db/migration:
V1__create_series.sql
V2__create_seasons.sql
V3__create_episodes.sql
V4__load_data.sql
Применим последнюю миграцию следующей командой:
flyway -url=jdbc:ydb:grpc://localhost:2136/local -locations=db/migration migrate
Результатом исполнения будет загрузка данных в таблицы series
, seasons
и episodes
:
Результат загрузки данных
Обновим схему путем добавления вторичного индекса:
db/migration:
V1__create_series.sql
V2__create_seasons.sql
V3__create_episodes.sql
V4__load_data.sql
V5__create_series_title_index.sql
Содержимое файла V5__create_series_title_index.sql
:
ALTER TABLE `series` ADD INDEX `title_index` GLOBAL ON (`title`);
Применим последнюю миграцию следующей командой:
flyway -url=jdbc:ydb:grpc://localhost:2136/local -locations=db/migration migrate
Результатом будет появление вторичного индекса у таблицы series
:
Cоздан индекс title_index
у таблицы series
info
Команда info печатает подробные сведения и информацию о состоянии всех миграций.
Добавим еще одну миграцию, которая переименовывает раннее добавленный вторичный индекс:
db/migration:
V1__create_series.sql
V2__create_seasons.sql
V3__create_episodes.sql
V4__load_data.sql
V5__create_series_title_index.sql
V6__rename_series_title_index.sql
Содержимое файла V6__rename_series_title_index.sql
:
ALTER TABLE `series` RENAME INDEX `title_index` TO `title_index_new`;
После исполнения следующей команды:
flyway -url=jdbc:ydb:grpc://localhost:2136/local -locations=db/migration info
Результатом будет подробная информация о состоянии миграций:
+-----------+---------+---------------------------+----------+---------------------+--------------------+----------+
| Category | Version | Description | Type | Installed On | State | Undoable |
+-----------+---------+---------------------------+----------+---------------------+--------------------+----------+
| Versioned | 1 | create series | SQL | | Below Baseline | No |
| Versioned | 2 | create seasons | SQL | | Below Baseline | No |
| Versioned | 3 | create episodes | SQL | | Ignored (Baseline) | No |
| | 3 | << Flyway Baseline >> | BASELINE | 2024-04-16 12:09:27 | Baseline | No |
| Versioned | 4 | load data | SQL | 2024-04-16 12:35:12 | Success | No |
| Versioned | 5 | create series title index | SQL | 2024-04-16 12:59:20 | Success | No |
| Versioned | 6 | rename series title index | SQL | | Pending | No |
+-----------+---------+---------------------------+----------+---------------------+--------------------+----------+
validate
Команда validate проверяет соответствие примененных миграций к миграциям, которые находятся в файловой системе пользователя.
После применения к текущим миграциям следующей команды:
flyway -url=jdbc:ydb:grpc://localhost:2136/local -locations=db/migration validate
В логах будет написано, что последняя миграция не была применена к нашей базе данных:
ERROR: Validate failed: Migrations have failed validation
Detected resolved migration not applied to database: 6.
To fix this error, either run migrate, or set -ignoreMigrationPatterns='*:pending'.
Давайте ее применим, выполнив flyway .. migrate
. Теперь валидация проходит успешно, вторичный индекс переименован.
Далее изменим файл уже ранее примененной миграции V4__load_date.sql
, удалив комментарии в SQL-скрипте.
После исполнения команды валидации, получим закономерную ошибку о том, что checksum
различается в измененной миграции:
ERROR: Validate failed: Migrations have failed validation
Migration checksum mismatch for migration version 4
-> Applied to database : 591649768
-> Resolved locally : 1923849782
repair
Команда repair пытается устранить выявленные ошибки и расхождения с таблицей историей схемы базы данных.
Устраним проблему с разными checksum
, выполнив следующую команду:
flyway -url=jdbc:ydb:grpc://localhost:2136/local -locations=db/migration repair
Результатом будет обновление колонки checksum
в таблице flyway_schema_history
у записи, отвечающей за миграцию V4__load_data.sql
:
Обновлено поле checksum
После восстановления таблицы лога валидация проходит успешно.
Также с помощью команды repair
можно удалить неудавшийся DDL-скрипт.
clean
Команда clean удаляет все таблицы в схеме базы данных.
Так как в отличие от других СУБД YDB не имеет такой сущности, как schema
, команда clean
удалит все таблицы в вашей базе данных.
Удалим все таблицы в нашей базе данных следующей командой:
flyway -url=jdbc:ydb:grpc://localhost:2136/local -locations=db/migration -cleanDisabled=false clean
Результатом будет пустая база данных:
Пустая база данных
Распределенные блокировка в Flyway
При определении новых миграций, которые еще не были применены в базе данных, Flyway берет распределенную блокировку перед их применением.
Здесь нет общепринятого решения, у каждой СУБД свое решение.
В Oracle и Derby:
LOCK TABLE flyway_history_schema IN EXCLUSIVE MODE
В PostgreSQL:
SELECT pg_try_advisory_xact_lock("flyway1")
Для YDB взятие блокировки похоже на подход CockroachDB, Google Spanner или Apache Ignite.
В таблицу истории миграций flyway_schema_history
клиент пытается вставить фиктивную запись c ID = -100:
INSERT INTO flyway_schema_history(installed_rank, version, description) VALUES (-100, $tableLockId, 'flyway-lock')
И кто успел вставить такую запись, тот и лидер.
Отпускается такая блокировка следующей SQL-командой:
DELETE FROM flyway_schema_history FROM installed_rank = -100
Важным отличием от CockroachDB и Google Spanner является то, что мы не проставляем тайм-аут истечения блокировки, а приостанавливаем процесс для расследования DBA, как сделано в Apache Ignite. Так как мы не знаем, в какой момент отказал лидер и в каком состоянии наша база данных.
А еще ScheduledThreadPool не закрывается, он нужен для процесса продления такой блокировки.
Поддержка и контакты
Если вы столкнулись с какой-то проблемой или у вас есть идея по улучшению диалекта YDB, то можно открыть issue в репозитории ydb-java-dialects с тегом flyway
.
Либо приходите обсудить её в публичный Telegram чат YDB.
Документация по Flyway находится по ссылке.