零停机迁移 Postgres 的正确方式
在这篇博文中,我们会介绍如何在零停机时间的前提下,使用 Bucardo 将 Postgres 数据库迁移到一个新实例上。我们将介绍如何避免常见的陷阱,比如数据丢失、性能下降和数据完整性故障等。我们已成功使用这一流程将我们的 Postgres 数据库从 9.5 版迁移到 Amazon RDS 上的 12.5 版,但该流程不只适用于 RDS,也不依赖 AWS 独有的任何内容。这种迁移策略应该能适用于任何自托管或托管的 Postgres。
分析
在本文中,我们将讨论将多个 Web 应用程序(如微服务)从一个数据库迁移到另一个的过程。现代软件架构由多个应用程序(或微服务)组成,而每个应用程序都有多个运行实例以增强扩展性。为了将你的应用程序移动到新的数据库,你必须首先确保两个数据库中的数据是同步的,并在任何给定时间点保持同步,否则你的客户端迟早会丢失数据,甚至陷入无效状态。
一个简单的解决方案是停止旧数据库的写入操作,获取快照,将其恢复到新的数据库,然后在新数据库中恢复操作。这种方案需要的停机时间太久,不适合生产环境。我们提到这一点只是为了做参考,因为这是确保你不会丢失任何数据的最简单方法,但用它的话,你可能会失去一些客户。
更现实的方法是在两个数据库之间设置一个近乎实时的双向复制,这样在理想情况下,应用程序可以同时向两者读取和写入,而不会注意到任何差异。你可以用这种方法一次一个实例地逐步移动你的应用程序,过程中不会停机,且不会影响用户。
由于我们希望应用程序能写入两个数据库,我们需要进行多主复制(multi-master replication)。在谷歌上搜索“Postgres 中的多主复制”可以找到大量解决方案,每种方案都有自己需要注意的优缺点。
我们决定继续使用Bucardo,因为它开源、速度快,并且提供了简单的监控和冲突解决机制。
Bucardo 的工作机制
Bucardo 充当两个 Postgres 实例之间的中间人。你可以让 Bucardo 在你喜欢的任何机器上运行,只要它可以访问源数据库和目标数据库即可。安装并设置多主复制后,Bucardo 将为你选择复制的所有表添加一些额外的触发器。
你运行 Bucardo 的实例在本地使用一个单独的 Postgresql 数据库以保存同步状态,这样你就可以随意暂停和重启同步过程。当发生更改时,触发器会将所有受影响的主键添加到 Bucardo 实例的 Postgres 中的“delta”表,另一个触发器将“启动(kick)”同步。每次同步被启动时,Bucardo 将对比所有主表中每个表的受影响行并选择一个获胜者,然后将更改同步到其余数据库。选择获胜者并不简单,此时可能会发生冲突。
如何同步漂移
你可以启动 Bucardo 同步,并使用autokick=0
标志告诉它在本地数据库中缓存所有漂移。不幸的是,虽然这个选项很关键,但它没有文档支持!这一步很关键,据我们所知唯一明确的参考资料出现在 David E. Wheeler 的这篇优秀博文中。
注意 autokick=0。这个标志确保了在记录增量时,它们不会被复制到任何地方,直到我们让 Bucardo 这样做为止。
使用这个标志,你就可以在本地缓存 Bucardo 实例中的增量,为你腾出了足够的时间来准备新数据库。这是非常关键的,尤其是对于大漂移更是如此。
实现
本节将展示我们遵循的步骤,以及每个步骤对应的脚本。我们已将代码上传到这个GitHub存储库,下文会对代码做具体拆解分析。
准备
-
启动一个新实例(在我们的例子中是 EC2)。该指令假设你运行的是 Debian 操作系统。
-
运行install.sh来安装 Bucardo
-
编辑vars.sh以设置你的数据库和 postgres 角色密码
-
在 shell 中导出上述变量:
$sourcevars.sh
-
(可选)如果你之前在源数据库中使用过 Bucardo,你可能需要运行uninstall_bucardo.sh来清除旧触发器。在运行之前,请查看我们根据我们的数据库生成的uninstall.template。你需要在那里列出你所有的表。
-
你需要手动运行
$ bucardo install
才能完成本地 Bucardo 安装。
迁移
仔细看看configure.sh脚本。在这里,你需要编辑脚本以匹配你的迁移方案。你需要为 Bucardo 对象定义描述性名称并指定排除的表或略过此选项。在你了解脚本的作用后可以继续运行它。该脚本执行以下操作:
-
设置
.pgpass
文件和一条 Bucardo 别名命令,以避免在此过程中要求你输入密码的交互式提示中断流程 -
配置 Bucardo 数据库、herds、数据库组和同步。如果你需要进一步了解 Bucardo 对象类型,他们的文档页面中有一个列表。
-
在新的 Postgresql 主机中初始化一个空数据库并运行此脚本创建用户。你需要编辑这个脚本来指定你的角色。密码由我们之前获取的
vars.sh
文件检索。 -
这一步只传输数据库模式,使用
pg_dump
并将其传输到新主机 -
使用本地缓存启动 Bucardo 同步
-
以压缩格式传输数据库数据。当数据传输和漂移开始堆积时,Bucardo 会将其保存在本地并在 autokick 标志更改值后重播
-
重置 autokick 标志的值以停止本地缓存,然后重新加载配置以让同步遵守新值
-
启动多主同步
现在持续同步已就位,是时候开始在新数据库中移动应用了。对我们来说,我们是更改配置服务器中的应用程序参数然后一一重新部署来完成这一步的。在这一步中,我们需要将旧数据库中的用户权限设置为只读。一旦我们应用的第一个实例连接到新数据库,我们就运行revoke_write_access_from_old_db.sql脚本更改旧数据库中的权限。这一步的时机非常重要。
迁移后检查
-
当你的同步运行时,你应该验证数据复制。我们使用分叉的 pgdatadiff工具来做到这一点。我们还进一步扩展了它,允许数据 diff 来排除表。
-
将所有应用切换到新数据库后,你可以停止 Bucardo 同步并下线它的机器。你应该再次运行uninstall_bucardo.sh以便从触发器清理你的新数据库。
总结
将你的 postgresql 数据库迁移到一个新实例会面临巨大挑战。无论你选择哪种工具来实施,你要面对的挑战都是一样的:
-
传输数据
-
在两个数据库之间设置多主复制
-
从业务角度处理冲突,确保数据一致性
-
验证同步过程
-
消除停机时间以避免干扰你的客户
在本文中,我们介绍了自己是如何解决这些问题的。我们遇到的一大困难是没有这方面的在线教程,因此我们不得不随机应变,并多次迭代我们的解决方案,直到我们正确地完成任务。我们也想听听你的反馈意见,这样可以帮助我们改进流程,并帮助可能面临相同问题的其他读者。
PS:背景故事
2020 年初,我们发现我们使用了两个 Postgres9.5 实例,我们从 Blueground 的早期就一直在使用它们。2020 年 1 月,我们不得不关闭旧实例并使用新实例,因为亚马逊即将迁移到新的 SSL/TLS 证书。这次迁移中,我们丢失了不少数据,花费了几天的时间来恢复它们。问题出在我们信任 Bucardo 的自动同步机制,让它处理我们的漂移;正如前面提到的那样,它有问题并且失败了。今年我们不得不再做一次,因为 Postgres 9.5 即将 EOL 了,否则它们会被 AWS 强行升级。这次我们下定决心要注意每一个小细节。我们相信我们可以快速、可靠且无故障地达成目标,我们做到了。
为什么要升级到新实例
首先,我们需要解释为什么我们不让亚马逊在没有我们干预的情况下在线升级我们的数据库。亚马逊提供了升级流程,但与迁移到新数据库实例的方案相比,它有一些严重的缺点:
-
AWSRDS 不为你提供即时回滚选项。在迁移过程中有两个实例,回滚是对我们应用的一个简单重配置,指向旧数据库。在整个过程中,这是一个非常重要的故障预防措施。
-
透明度。如果 RDS 升级数据库失败、出现延迟或性能问题,我们根本无法采取任何措施。在生产环境中,你需要有一个可靠的回滚计划,以防万一。
-
我们想要的某些功能在当前实例中不可用,例如静态加密和 RDS 见解。
-
在某些情况下,我们需要更改实例类型。
我们选择 Bucardo 是因为我们想要一个在我们的 VPC 中沙盒化的解决方案,这样生产数据永远不会泄露到互联网上。最后迁移很成功,也没有丢失数据。迁移过程的总耗时不到 2 小时,算是比较成功的!
原文链接:
https://engineering.theblueground.com/blog/zero-downtime-postgres-migration-done-right/
本文文字及图片出自 InfoQ
共有 1 条讨论