«1. Обзор

Переносные миграции не всегда идут по плану. В этом руководстве мы рассмотрим доступные нам варианты восстановления после неудачной миграции.

2. Настройка

Давайте начнем с базового проекта Spring Boot, сконфигурированного для Flyway. Он имеет зависимости от flyway-core, spring-boot-starter-jdbc и flyway-maven-plugin.

Дополнительные сведения о конфигурации см. в нашей статье, посвященной Flyway.

2.1. Конфигурация

Во-первых, давайте добавим два разных профиля. Это позволит нам легко запускать миграции для разных механизмов баз данных:

<profile>
    <id>h2</id>
    <activation>
        <activeByDefault>true</activeByDefault>
    </activation>
    <dependencies>
        <dependency>
            <groupId>com.h2database</groupId>
	    <artifactId>h2</artifactId>
        </dependency>
    </dependencies>
</profile>
<profile>
    <id>postgre</id>
    <dependencies>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>
    </dependencies>
</profile>

Давайте также добавим файлы конфигурации базы данных Flyway для каждого из этих профилей.

Во-первых, мы создаем application-h2.properties:

flyway.url=jdbc:h2:file:./testdb;DB_CLOSE_ON_EXIT=FALSE;AUTO_RECONNECT=TRUE;MODE=MySQL;DATABASE_TO_UPPER=false;
flyway.user=testuser
flyway.password=password

И после этого давайте создадим приложение PostgreSQL application-postgre.properties:

flyway.url=jdbc:postgresql://127.0.0.1:5431/testdb
flyway.user=testuser
flyway.password=password

Примечание. Мы можем изменить конфигурацию PostgreSQL на соответствовать уже существующей базе данных, или мы можем использовать файл docker-compose в примере кода.

2.2. Миграции

Давайте добавим наш первый файл миграции, V1_0__add_table.sql:

create table table_one (
  id numeric primary key
);

Теперь добавим второй файл миграции, содержащий ошибку, V1_1__add_table.sql:

create table <span style="color: #ff0000">table_one</span> (
  id numeric primary key
);

Мы допустили ошибку в цели, используя одно и то же имя таблицы. Это должно привести к ошибке миграции Flyway.

3. Запустите миграции

Теперь давайте запустим приложение и попробуем применить миграции.

Сначала для профиля h2 по умолчанию:

mvn spring-boot:run

Затем для профиля postgre:

mvn spring-boot:run -Ppostgre

Как и ожидалось, первая миграция прошла успешно, а вторая не удалась:

Migration V1_1__add_table.sql failed
...
Message    : Table "TABLE_ONE" already exists; SQL statement:

3.1. Проверка состояния

Перед тем, как перейти к восстановлению базы данных, давайте проверим состояние миграции Flyway, выполнив:

mvn flyway:info -Ph2

Это вернет, как и ожидалось:

+-----------+---------+-------------+------+---------------------+---------+
| Category  | Version | Description | Type | Installed On        | State   |
+-----------+---------+-------------+------+---------------------+---------+
| Versioned | 1.0     | add table   | SQL  | 2020-07-17 12:57:35 | Success |
| Versioned | 1.1     | add table   | SQL  | 2020-07-17 12:57:35 | <span style="color: #ff0000">Failed</span>  |
+-----------+---------+-------------+------+---------------------+---------+

:

mvn flyway:info -Ppostgre

Мы заметили, что вторая миграция находится в состоянии Pending, а не Failed:

+-----------+---------+-------------+------+---------------------+---------+
| Category  | Version | Description | Type | Installed On        | State   |
+-----------+---------+-------------+------+---------------------+---------+
| Versioned | 1.0     | add table   | SQL  | 2020-07-17 12:57:48 | Success |
| Versioned | 1.1     | add table   | SQL  |                     | <span style="color: #339966">Pending</span> |
+-----------+---------+-------------+------+---------------------+---------+

Разница заключается в том, что PostgreSQL поддерживает транзакции DDL, а другие, такие как H2 или MySQL, — нет. В результате PostgreSQL смог откатить транзакцию неудачной миграции. Давайте посмотрим, как эта разница влияет на вещи, когда мы пытаемся восстановить базу данных.

3.2. Исправьте ошибку и повторите миграцию

Давайте исправим файл миграции V1_1__add_table.sql, изменив имя таблицы с table_one на table_two.

Теперь давайте попробуем снова запустить приложение:

mvn spring-boot:run -Ph2

Теперь мы замечаем, что миграция H2 завершается с ошибкой:

Validate failed: 
Detected failed migration to version 1.1 (add table)

Flyway не будет повторно запускать миграцию версии 1.1, если уже для этой версии существует неудачная миграция.

С другой стороны, профиль postgre успешно запустился. Как было сказано ранее, из-за отката состояние было чистым и готовым к применению исправленной миграции.

Действительно, запустив mvn flyway:info -Ppostgre, мы увидим, что обе миграции применены с успехом. Итак, в заключение, для PostgreSQL все, что нам нужно было сделать, это исправить наш сценарий миграции и повторно запустить миграцию.

4. Восстановление состояния базы данных вручную

Первый подход к восстановлению состояния базы данных заключается в ручном удалении записи Flyway из таблицы flyway_schema_history.

Давайте просто запустим эту инструкцию SQL для базы данных:

delete from flyway_schema_history where version = '1.1';

Теперь, когда мы снова запустим mvn spring-boot:run, мы увидим успешное применение миграции.

Однако прямое управление базой данных может быть не идеальным решением. Итак, давайте посмотрим, какие еще варианты у нас есть.

5. Ремонт пролетного пути

5.1. Исправление неудачной миграции

Давайте продолжим, добавим еще один поврежденный файл миграции V1_2__add_table.sql, запустим приложение и вернемся к состоянию, в котором произошла ошибка миграции.

Еще один способ восстановить состояние базы данных — использовать инструмент flyway:repair. После исправления файла SQL вместо того, чтобы вручную трогать таблицу flyway_schema_history, мы можем вместо этого запустить:

mvn flyway:repair

что приведет к:

Successfully repaired schema history table "PUBLIC"."flyway_schema_history"

За кулисами Flyway просто удалит запись о неудачной миграции из flyway_schema_history Таблица.

«Теперь мы можем снова запустить flyway:info и увидеть, что состояние последней миграции изменилось с Failed на Pending.

Давайте снова запустим приложение. Как мы видим, исправленная миграция теперь успешно применяется.

5.2. Realign Checksums

Обычно рекомендуется никогда не изменять успешно примененные миграции. Но могут быть случаи, когда нет никакого способа обойти это.

Итак, в таком случае давайте изменим миграцию V1_1__add_table.sql, добавив комментарий в начало файла.

Запустив приложение сейчас, мы видим сообщение об ошибке «Несоответствие контрольной суммы миграции», например:

Migration checksum mismatch for migration version 1.1
-> Applied to database : 314944264
-> Resolved locally    : 1304013179

Это происходит потому, что мы изменили уже примененную миграцию, и Flyway обнаруживает несоответствие.

Чтобы выровнять контрольные суммы, мы можем использовать ту же команду flyway:repair. Однако на этот раз миграция выполняться не будет. Только контрольная сумма записи версии 1.1 в таблице flyway_schema_history будет обновлена, чтобы отразить обновленный файл миграции.

Запустив приложение снова после восстановления, мы замечаем, что приложение теперь запускается успешно.

Обратите внимание, что в этом случае мы использовали flyway:repair через Maven. Другой способ — установить инструмент командной строки Flyway и запустить восстановление flyway. Эффект тот же: исправление пролетного пути удалит неудачные миграции из таблицы flyway_schema_history и перестроит контрольные суммы уже примененных миграций.

6. Обратные вызовы Flyway

Если мы не хотим вмешиваться вручную, мы могли бы рассмотреть подход к автоматической очистке неудачных записей из flyway_schema_history после неудачной миграции. Для этой цели мы можем использовать обратный вызов afterMigrateError Flyway.

Сначала мы создаем файл обратного вызова SQL db/callback/afterMigrateError__repair.sql:

DELETE FROM flyway_schema_history WHERE success=false;

Это автоматически удалит любую ошибочную запись из истории состояния Flyway при возникновении ошибки миграции.

Давайте создадим конфигурацию профиля application-callbacks.properties, которая будет включать папку db/callback в список местоположений Flyway:

spring.flyway.locations=classpath:db/migration,classpath:db/callback

И теперь, после добавления еще одной сломанной миграции V1_3__add_table.sql, мы запускаем приложение включая профиль обратных вызовов:

mvn spring-boot:run -Dspring-boot.run.profiles=h2,callbacks
...
Migrating schema "PUBLIC" to version 1.3 - add table
Migration of schema "PUBLIC" to version 1.3 - add table failed!
...
Executing SQL callback: afterMigrateError - repair

Как и ожидалось, миграция завершилась неудачно, но обратный вызов afterMigrateError запустился и очистил flyway_schema_history.

Простого исправления файла миграции V1_3__add_table.sql и повторного запуска приложения будет достаточно, чтобы применить исправленную миграцию.

7. Резюме

В этой статье мы рассмотрели различные способы восстановления после неудачной миграции Flyway.

Мы видели, как такая база данных, как PostgreSQL, то есть та, которая поддерживает транзакции DDL, не требует дополнительных усилий для восстановления состояния базы данных Flyway.

С другой стороны, для таких баз данных, как H2, без этой поддержки, мы увидели, как восстановление Flyway можно использовать для очистки истории Flyway и, в конечном итоге, применения исправленной миграции.

Как всегда, полный код доступен на GitHub.