如何使用多个应用程序实例安全地运行数据库迁移?


10

我们有一个应用程序,它既有快速的迁移(<1秒)又有缓慢的数据库迁移(> 30秒)。现在,我们正在将数据库迁移作为CI的一部分来运行,但是我们的CI工具必须知道我们的应用程序(在多个环境中)的所有数据库连接字符串,这并不理想。我们希望更改此过程,以便应用程序在启动时运行自己的数据库迁移。

情况如下:

我们有此应用程序的多个实例-在生产中大约有5个实例。我们称他们为node1, ..., node5。每个应用程序都连接到一个SQL Server实例,并且我们不使用滚动部署(就我所知,所有应用程序都是同时部署的)

问题:说我们的迁移时间很长。在这种情况下,先node1启动,然后开始执行迁移。现在node4开始,并且长时间运行的迁移尚未完成,因此node4也开始运行迁移->可能的数据损坏?您将如何预防该问题,或者该问题甚至足够重要而值得担心?

我当时正在考虑使用分布式锁(使用etcd或类似的方法)解决此问题。基本上,所有应用程序都尝试获取锁,只有其中一个可以获取并运行迁移,然后解锁。当其余的应用程序启动并进入关键部分时,所有迁移都已运行,因此迁移脚本将退出。

但是,我的直觉是说:“这太过分了,必须有一个更简单的解决方案。”因此,我想问一下这里是否有其他更好的主意。


1
使用“迁移状态”表作为全局/分布式锁怎么样?单行将指示迁移当前是否处于活动状态,以及可能最后执行了什么迁移。
巴特·范·英根·谢瑙

您需要异步部署应用程序吗?

Answers:


4

既然您提到了SQL Server:根据以前的DBA.SE文章,可以(并且应该)将模式更改放入事务中。这使您能够像设计对数据库的任何其他形式的并发写入一样设计迁移,即开始事务,当事务失败时,将其回滚。这样至少可以避免某些最严重的数据库损坏情况(尽管在进行破坏性的迁移步骤(例如删除列或表)时,仅凭事务本身无法防止数据丢失)。

到目前为止,我确定您还将需要一些migrations用于注册已应用的迁移的表,以便应用程序进程可以检查是否已应用特定的迁移。然后利用“ SELECT FOR UPDATE”来实现您的迁移,如下所示(伪代码):

  • 开始交易
  • SELECT FROM Migrations FOR UPDATE WHERE MigrationLabel='MyMigration42'
  • 如果前一条语句返回一个值,则结束事务
  • 应用迁移(如果失败则回滚,记录失败并结束事务)
  • INSERT 'MyMigration42' INTO Migrations(MigrationLabel)
  • 结束交易

这将锁定机制直接构建到“是否已应用迁移”测试中。

请注意,从理论上讲,此设计将使您的迁移步骤不知道哪个应用程序实际应用了它-有可能步骤1由app1应用,步骤2由app2应用,步骤3由app 3应用,步骤4由app1应用再次,依此类推。但是,只要正在使用其他应用程序实例,也不应用迁移也是一个好主意。如您的问题中所述,并行部署可能已经考虑了此约束。


1

也许您可以找到一个支持多个节点数据库迁移的库。

我了解Java世界中的两个库,它们都支持您所需要的:

  • Liquibase:来自其常见问题解答Liquibase使用分布式锁定系统,一次只允许一个进程更新数据库。其他进程将只等到锁已释放。
  • Flyway:从其下载页面并行可安全用于多个节点✓

可能还有其他用于Java和其他语言的工具。


如果您无法(或不想)使用这样的工具,则可以将表用作锁,甚至用作迁移日志,有关示例,请参阅Doc Browns的答案

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.