Как безопасно запустить миграцию базы данных с несколькими экземплярами приложения?

10

У нас есть приложение, которое сочетает в себе как быструю (<1 секунда), так и медленную миграцию базы данных (> 30 секунд). Прямо сейчас мы выполняем миграцию базы данных как часть CI, но затем наш инструмент CI должен знать все строки подключения к базе данных для нашего приложения (в разных средах), что не идеально. Мы хотим изменить этот процесс, чтобы приложение запускало собственные миграции базы данных при запуске.

Вот ситуация:

У нас есть несколько экземпляров этого приложения - около 5 в производстве. Давайте позвоним им node1, ..., node5. Каждое приложение подключается к одному экземпляру SQL Server, и мы не используем скользящее развертывание (насколько я знаю, все приложения развертываются одновременно)

Проблема: скажем, у нас длительная миграция. В этом случае node1запускается, а затем начинается выполнение миграции. Теперь node4запускается, и длительная миграция еще не завершена, поэтому node4также запускается миграция -> возможно ли повреждение данных? Как бы вы предотвратили эту проблему или эта проблема настолько важна, чтобы о ней беспокоиться?

Я думал о решении этой проблемы с распределенной блокировкой (используя etcdили что-то в этом роде). По сути, все приложения пытаются получить блокировку, только одно из них получает ее и выполняет миграции, а затем разблокирует. Когда остальные приложения запускаются и входят в критическую секцию, все миграции уже выполнены, поэтому скрипт миграции просто завершается.

Тем не менее, моя интуиция говорит: «Это излишне, должно быть более простое решение», поэтому я подумал, что попрошу здесь, чтобы узнать, есть ли у кого-нибудь еще идеи получше.

Бен
источник
1
Как насчет использования таблицы «статус миграции» в качестве глобальной / распределенной блокировки? В одной строке будет указано, активна ли миграция и, возможно, какая миграция была выполнена последней.
Барт ван Инген Шенау
Вам нужно развернуть свои приложения асинхронно?
Бен

Ответы:

4

Поскольку вы упомянули SQL-сервер: согласно этому предыдущему сообщению DBA.SE , изменения схемы могут (и должны) вноситься в транзакции. Это дает вам возможность спроектировать ваши миграции точно так же, как и любые другие формы одновременной записи в вашу БД - вы запускаете транзакцию, а когда она терпит неудачу, вы откатываете ее назад. Это предотвращает, по крайней мере, некоторые из худших сценариев повреждения базы данных (хотя одни только транзакции не предотвратят потерю данных, когда существуют разрушительные шаги миграции, такие как удаление столбца или таблицы).

До сих пор я уверен, что вам также понадобится migrationsтаблица, в которой зарегистрированы уже примененные миграции, поэтому процесс приложения может проверить, была ли применена конкретная миграция или нет. Затем используйте «SELECT FOR UPDATE» для реализации ваших миграций следующим образом (псевдокод):

  • Начать транзакцию
  • SELECT FROM Migrations FOR UPDATE WHERE MigrationLabel='MyMigration42'
  • если предыдущий оператор возвращает значение, завершите транзакцию
  • применить миграцию (откатить в случае сбоя, зарегистрировать сбой и завершить транзакцию)
  • INSERT 'MyMigration42' INTO Migrations(MigrationLabel)
  • завершить транзакцию

Это встраивает механизм блокировки непосредственно в тест «была ли миграция уже применена» .

Обратите внимание, что этот дизайн - в теории - позволит вашим шагам миграции не знать, какое приложение на самом деле его применяет - может быть возможно, что шаг 1 будет применен приложением 1, шаг 2 приложением 2, шаг 3 приложением 3, шаг 4 приложением 1 снова и так далее. Тем не менее, это также хорошая идея не применять миграцию, пока используются другие экземпляры приложения. Параллельное развертывание, как уже упоминалось в вашем вопросе, может уже устранить это ограничение.

Док Браун
источник
1

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

Я знаю о двух библиотеках в мире Java, обе они поддерживают то, что вам нужно:

  • Liquibase : Из их часто задаваемых вопросов : Liquibase использует систему распределенной блокировки, чтобы позволить только одному процессу обновлять базу данных за один раз. Другие процессы просто будут ждать, пока блокировка не будет снята.
  • Flyway : со страницы загрузки : безопасно для нескольких узлов параллельно ✓

Вероятно, есть и другие инструменты для Java и других языков.


Если вы не можете (или не хотите) использовать такой инструмент, таблицу можно использовать как блокировку или даже как журнал миграции, см. Пример Док Браунс .

siegi
источник