速来报名!AICon北京站鸿蒙专场~ 了解详情
写点什么

零停机迁移 Postgres 的正确方式

  • 2021-07-06
  • 本文字数:5449 字

    阅读完需:约 18 分钟

零停机迁移 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 的正确方法是获取源数据库的快照,将其恢复到新的数据库,然后启动一个多主 Bucardo 同步。不要那样做!


如果这样做,你将丢失与当前数据库大小和写入流量成正比的数据。这是因为获取快照并恢复它需要大量时间。在这段时间里,源数据库将因为数据写入而开始漂移,并且这种漂移也必须同步以确保两个主数据库包含相同的数据。这里的问题是人们相信 Bucardo 会做某种回填,但事实证明它在这项任务上不可靠,并且可能无法同步大的漂移。你自然可以使用跨数据库对比数据的工具,确保消除偏差;但如果数据集很大,这样做会浪费大量时间,而恰恰我们追求的就是零停机时间。


此外,如果复制延迟足够大,正在进行的同步可能会被误报为漂移。


如何同步漂移


你可以启动 Bucardo 同步,并使用autokick=0标志告诉它在本地数据库中缓存所有漂移。不幸的是,虽然这个选项很关键,但它没有文档支持!这一步很关键,据我们所知唯一明确的参考资料出现在 David E. Wheeler 的这篇优秀博文中。


注意 autokick=0。这个标志确保了在记录增量时,它们不会被复制到任何地方,直到我们让 Bucardo 这样做为止。


使用这个标志,你就可以在本地缓存 Bucardo 实例中的增量,为你腾出了足够的时间来准备新数据库。这是非常关键的,尤其是对于大漂移更是如此。


如何引导新数据库


这里有两个选项。你可以从第一个数据库中获取全包快照并将其恢复到新实例,或者你可以从一个新的空数据库开始,然后分别传输用户、模式和数据(按这个顺序)。我们推荐后一种方法。原因是在对两个解决方案进行基准测试对比后,第二个的结果更干净。我们可以从头开始关闭旧用户帐户和临时表并细化用户权限。


如果你使用的是 AWS RDS,推荐的这个方案也会更快。获取快照可能需要几分钟时间,具体取决于你的数据库大小。


此外,如果你像我们一样从未加密的服务器迁移到使用静态加密的服务器,你需要获取快照、加密快照,然后将其还原到新的 RDS 实例。这样做用的时间更久,而最小化迁移时间是我们的一个关键目标。

选择性同步


在开始 Bucardo 同步前,你需要正确配置它。你需要指定两个数据库、它们的类型(主/副本),还有指定数据库的哪些部分应包含在同步中。你可以从一个模式(schema)中批量添加所有表,数据库有很多表的时候这个办法非常有用。


Bucardo 无法在没有主键(PK)的情况下同步表,这很正常,因为那种情况下它无法区分唯一条目。我们不得不在流程中排除一些表,这些表充当各种表迁移的缓存并且不包含 PK。一些未使用的表也被排除在外,因此我们没有将未使用的数据传输到新数据库。在 Bucardo 中很容易完成上述操作:添加所有表后,你可以移除要排除的表。

迁移用户


Bucardo 不会迁移 Postgres 用户,你需要手动转移你的用户帐户。我们为此编写了一个脚本。这个脚本会到新数据库,使用从配置服务器检索到的密码创建新用户,然后设置他们的权限。尽管你可能不会将数据存储为代码,但将用户保存为代码是一种很好的做法,这样在发生灾难时就能够恢复它们了。

迁移模式和数据


你可以使用 Postgres 及其pg_dump/pg_restore工具来传输你的模式和数据。这个步骤很简单,但有一个要点。请记住,此时我们已经启动并运行了 Bucardo 来记录漂移,因此在目标服务器上恢复数据将被解释为同步回源数据库的更改。这就是为什么我们需要启用 session_replication_role=replica标志,使用一个副本会话将数据恢复到目标 Postgres 数据库。在我们启动你的持续同步之前,我们需要禁用它。

冲突


高可用性是零停机迁移的先决条件,它通常要求每个应用程序有多个正在运行的实例。一般来说,每个实例都应该在重新启动之前排空,因此无法在完全相同的时间点将所有实例切换到新数据库。所以总会有一个关键的——或短或长的——时间窗口,在这个窗口中同一个应用程序将同时写入两个数据库,并且在这段时间内可能会发生冲突。


冲突很少见,因为它们需要在两个数据库中进行两次写入,然后 Bucardo 才能复制这两个记录。复制时间接近于零,你可能根本不会遇到任何冲突,但这种迁移发生在关键的生产环境中,因此不能忽略它们。


想象一下,两个客户试图在同一天预订同一所房子。如果他们同时尝试这样做并且每个用户都指向不同的数据库,则可能会发生冲突。Bucardo 有一个冲突解决机制,提供了两个基本选项:要么让 Bucardo 自动处理冲突(默认选项),要么中止同步并手动解决它们。这是迁移过程中最关键的部分,我们进一步分析一下。


如果你的表有一个自动递增的 ID 作为主键,Postgres 会自动从相应的序列中选择下一个 ID。Bucardo 也会同步序列。假设在上面的示例中,你有一个带有自动递增 ID 作为 PK 的 bookings 表,并且最新的记录 ID 是 42。这里会发生并发插入,并且在两个数据库中创建两条不同的记录,它们都以 43 作为 PK,但数据不同。如果你让 Bucardo 处理冲突,它会只保留最新的一个并删除另一个。最后你会丢失一个对你的客户来说似乎是成功的预订。你的数据库仍处于有效状态,但你会丢失数据,还没法恢复。这是一个死胡同!


在讨论解决方案之前,让我们考虑另一种情况。假设你的表使用 UUID 作为 PK。回放上面的场景,并发预订将在两个数据库中创建两个不同的记录,并具有两个不同的 PK。这次没有发生冲突。Bucardo 将成功同步两个数据库中的两条记录,但从业务角度来看你的数据仍然无效,因为你不能两次预订同一所房子。因此这里很明显,从业务角度来看数据库有效性并不能保证你的数据有效。你需要小心对待冲突的处理方式,以免你的客户遇到问题。


Bucardo 支持自定义解析策略。你可以根据业务需求制定自己的策略,但这很快就会变得过于复杂和耗时。另一种方法是创建你自己的工具来检测和解决迁移期间的数据违规问题。这并非易事:它必须根据数据的复杂程度来做设计,并且可能需要大量开发工作。


我们的解决方案是在开始迁移之前满足两个条件,来彻底避免冲突。


首先,我们努力最小化数据库之间的转换时间,以最小化冲突概率。为了做到这一点,我们会修改应用的重配置脚本以指向新的数据库,一次一个实例,但所有的不同应用会并行操作。


第二步最关键,就在我们开始将应用切换到新数据库之前,我们撤销了旧数据库中应用用户的写入权限。通过这种方式,我们可以彻底避免冲突,但代价是一定比例的数据库写入失败时间。这当然需要你的应用程序能够优雅地处理失败的数据库写入。你的应用程序执行此操作时应该能独立于任何数据库迁移活动,因为这对于生产环境来说至关重要。


下面就是最终的迁移计划:

实现


本节将展示我们遵循的步骤,以及每个步骤对应的脚本。我们已将代码上传到这个GitHub存储库,下文会对代码做具体拆解分析。

准备

  1. 启动一个新实例(在我们的例子中是 EC2)。该指令假设你运行的是 Debian 操作系统。

  2. 运行install.sh来安装 Bucardo

  3. 编辑vars.sh以设置你的数据库和 postgres 角色密码

  4. 在 shell 中导出上述变量:$sourcevars.sh

  5. (可选)如果你之前在源数据库中使用过 Bucardo,你可能需要运行uninstall_bucardo.sh来清除旧触发器。在运行之前,请查看我们根据我们的数据库生成的uninstall.template。你需要在那里列出你所有的表。

  6. 你需要手动运行$ bucardo install才能完成本地 Bucardo 安装。

迁移

仔细看看configure.sh脚本。在这里,你需要编辑脚本以匹配你的迁移方案。你需要为 Bucardo 对象定义描述性名称并指定排除的表或略过此选项。在你了解脚本的作用后可以继续运行它。该脚本执行以下操作:

  1. 设置.pgpass文件和一条 Bucardo 别名命令,以避免在此过程中要求你输入密码的交互式提示中断流程

  2. 配置 Bucardo 数据库、herds数据库组同步。如果你需要进一步了解 Bucardo 对象类型,他们的文档页面中有一个列表

  3. 在新的 Postgresql 主机中初始化一个空数据库并运行此脚本创建用户。你需要编辑这个脚本来指定你的角色。密码由我们之前获取的vars.sh文件检索。

  4. 这一步只传输数据库模式,使用pg_dump并将其传输到新主机

  5. 使用本地缓存启动 Bucardo 同步

  6. 以压缩格式传输数据库数据。当数据传输和漂移开始堆积时,Bucardo 会将其保存在本地并在 autokick 标志更改值后重播

  7. 重置 autokick 标志的值以停止本地缓存,然后重新加载配置以让同步遵守新值

  8. 启动多主同步

现在持续同步已就位,是时候开始在新数据库中移动应用了。对我们来说,我们是更改配置服务器中的应用程序参数然后一一重新部署来完成这一步的。在这一步中,我们需要将旧数据库中的用户权限设置为只读。一旦我们应用的第一个实例连接到新数据库,我们就运行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/

2021-07-06 14:182432
用户头像
王强 技术是文明进步的力量

发布了 834 篇内容, 共 442.7 次阅读, 收获喜欢 1753 次。

关注

评论

发布
暂无评论
发现更多内容

Spring AOP(二) 修饰者模式和JDK Proxy

程序员历小冰

spring Java、 28天写作 spring aop 12月日更

升级你的高手秘籍,在找到规律的同时,你需要把时间整个明白。

叶小鍵

9.《重学 JAVA》-- 控制语句(一)

杨鹏Geek

Java 25 周年 28天写作 12月日更

「如何从0到1实现一个基于vite的前端基础库👾」

速冻鱼

前端 Node 签约计划第二季 12月日更

设计消息队列存储消息数据的 MySQL 表格

胡颖

Volatile 原理(二)

悟空聊架构

volatile 28天写作 可见性 悟空聊架构 12月日更

解决:standard_init_linux.go:219: exec user process caused

liuzhen007

28天写作 12月日更

聊聊今天 log4j 的大瓜

Justin

漏洞 闲聊 28天写作

记录-最骄傲的事(3)

将军-技术演讲力教练

我们一起,盘点 2021 十大技术领域的全部精彩

InfoQ写作社区官方

大数据 云原生 编程语言 话题讨论 2021年度技术盘点与展望

JavaScript 中8 个最佳电子邮件库

devpoint

JavaScript nodejs 12月日更 email pop3

在线将JS/JavaScript-Object转JSON工具

入门小站

工具

盘点2021:一年读完的50本书

石云升

书单 年终总结 28天写作 12月日更 盘点2021

当诗人遇到熟读2600亿中文参数的大模型

白洞计划

hmily学习笔记

风翱

12月日更 Hmily

想象与实践的过程

Nydia

设计电商秒杀系统

Rabbit

团队基建系列 - 组织知识传承 5 底层逻辑

搬砖的周狮傅

团队 团队成长

RPC学习笔记

风翱

RPC 12月日更

架构实战营第 4 期 -- 模块二作业

烈火干柴烛灭田边残月

架构实战营

Hoo虎符研究院 | 币海寻珠——2021年区块链投融大事记

区块链前沿News

区块链 虎符 Hoo虎符 Hoo 虎符交易所

zookeeper的数据同步是如何完成的?

卢卡多多

zookeeper 28天写作 12月日更

.NET内存管理必备知识

喵叔

28天写作 12月日更

[Pulsar] Batch message的确认

Zike Yang

Apache Pulsar 12月日更

Maven进阶(四):Maven 常用命令

No Silver Bullet

maven 12月日更

Helm 快速入门

xcbeyond

Helm 28天写作 12月日更

你不得不掌握的前端提交规范(git cz)

你好bk

JavaScript 前端 代码注释 代码规范 12月日更

Prometheus Exporter (二十二)Infiniband Exporter

耳东@Erdong

Prometheus 28天写作 exporter 12月日更 Infiniband

什么是网络安全?网络安全威胁存在哪些?

喀拉峻

网络安全

中小型研发团队的一种考核思路

wood

团队管理 28天写作 研发考核

Eureka基础

李子捌

微服务 28天写作 12月日更

零停机迁移 Postgres的正确方式_数据库_RIGAS PAPATHANASOPOULOS_InfoQ精选文章