假设我有一个在 Kubernetes 上运行的系统,使用滚动更新或使用蓝/绿部署的 AWS ECS 或任何其他提供零停机部署的解决方案。这里的关键点是新版本和现有版本可以共存,并且它们使用相同的关系数据库。
我们在数据库中有一个名为
users
的表,其中有一个字段 username
,我们希望在新版本中将其拆分为两个单独的字段:firstname
和 lastname
。为此,我们相应地更改服务代码并迁移现有记录。这种情况也适合潜在的回滚。这种情况还应该考虑到潜在的回滚。
在部署过程中,我们首先运行迁移,然后运行滚动更新。据我了解,这可能会导致迁移完成,但以前版本的服务仍在消耗流量的情况。这就是我们以某种方式进行迁移的原因,它支持使用当前版本和新版本。我们添加了新字段,但尚未删除旧字段。
username
字段将在以后的版本中删除。
部署完成后,旧版本将被禁用,只有新版本在运行,分别保存
firstname
和lastname
。但这里我们最终遇到一种情况,如果在过渡期间以前的版本存储了一条记录,它将存储在 username
字段中。这些记录不是数据库迁移的主题,因为它们是在 in 启动后创建的。
如何克服这种情况?数据库迁移(仅数据迁移部分)是否应该在部署后启动,或者所描述的过程中可能存在差距?
这实际上是一个很好的问题,需要根据升级的方式来设计。
最常见的是通过在升级期间锁定客户端来解决此问题。一种方法是这样的:
您提到了蓝/绿或其他零停机部署,所以我认为这是这里的重要部分。不是说可以做到,而是做到零停机。
您可以控制新应用程序,为什么不让它同时处理已迁移和尚未迁移的数据呢?不要在交换之前迁移数据,而是在交换之后迁移。任何新数据都会正确插入,而旧数据会被迁移,并且应用程序通过在运行时拆分用户名字段来处理丢失的数据。然后在稍后删除该列时删除此版本处理代码。可能有点困难,但目标是零中断,而不是最简单的方法。
如果您的数据库有插入触发器:将列添加到表后,您可以添加一个插入触发器,该触发器在插入用户名列时触发。触发器本身将执行与迁移相同的拆分逻辑,并在从旧应用程序插入新数据时更新名字和姓氏列。正在更新(而不是插入)的迁移不会触发触发器,并且新的应用程序代码将不包含用户名列,因此它也不会触发触发器。这将允许您在旧应用程序运行时运行迁移,然后切换到新应用程序,而无需第一种方法中的附加应用程序代码。当您删除当前未使用的用户名列时,只需删除当前未使用的触发器即可。
我认为重要的是,确保您的更新不会使用适当的批处理技术进行过多的独占锁。阻塞读取操作会迅速陷入一个巨大的阻塞进程队列,从而导致数据库服务器瘫痪。使用保存时间非常短的小型索引更新批次。这可能会使迁移花费更长的时间,但谁在乎网站是否运行良好,并且任何生成的读取块仅保留几毫秒,而用户不会注意到。