大规模并行 Postgres 备份

Hacker News Top 工具

摘要

PlanetScale 描述了它如何通过为每个分片启动 EC2 实例、从对象存储恢复之前的备份并重放 WAL,来对分片 Postgres 数据库执行大规模并行备份,从而实现超过 50 GB/s 的 PB 级备份速度。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/08/03 19:34

# 大规模并行 Postgres 备份 —— PlanetScale 来源:https://planetscale.com/blog/massively-parallel-postgres-backups 每 12 小时,一个备份系统必须将繁忙数据库的整个状态转换为一致的、加密的快照,并且对生产查询零影响。 这样的备份至关重要,同时也是大多数工程师宁愿永远不需要考虑的事情。 *只要让备份正常工作就好*。 我们在 PlanetScale 的目标是让 Postgres 和 MySQL 备份的创建、调度、管理和恢复变得毫不费力。 尽管这是客户从外部体验到的,但在内部实现这一点需要精心编排云基础设施和 DBMS 工具。 尤其有趣的是分片数据库的备份,这需要启动备份专用节点、从对象存储拉取数据,以及 WAL 重放,所有这些都以大规模并行方式进行。这些技术使得 PB 级数据库可以在数小时内完成备份,速率超过 50 GB/s。 在这里,我们将带您一窥幕后,了解如何以大规模并行方式有效备份分片数据库。 ## 备份生命周期 (https://planetscale.com/blog/massively-parallel-postgres-backups#the-backup-lifecycle) 这里有一个 Neki(分片 Postgres)数据库的例子,它有 8 个分片,正在愉快地处理每秒数十万次的生产查询。 如果你对分片这个概念还不熟悉,可以查看我们最近发布的文章 [让 768 台服务器看起来像 1 台 (https://planetscale.com/blog/making-768-servers-look-like-1)](https://planetscale.com/blog/making-768-servers-look-like-1),了解其工作原理。 进行备份的第一步取决于这是*首次备份*还是之前已经备份过。我们先从稳态情况开始,即假设先前已有一个健康的备份被捕获并存储在 Amazon S3(或其他云中的类似对象存储)中。 由于分片 Postgres 数据库是许多独立的主 Postgres 服务器协同工作,我们使用常规的 Postgres 备份作为 Neki 大规模备份的构建块。 在 Postgres 中有三种备份方式,我们之前已经[详细写过相关文章 (https://planetscale.com/blog/postgres-backups-under-the-hood)](https://planetscale.com/blog/postgres-backups-under-the-hood)。其中最好的一种,也是 Neki 使用的方式,是将文件系统备份与归档的 Write-Ahead Log(WAL)重放相结合。总结起来,步骤如下: 1. 在时间 `T1` 开始对磁盘上的 Postgres 数据库文件进行完整备份 2. 备份在时间 `T2` 完成;在 `T1` 和 `T2` 之间,磁盘上的行可能已被修改 3. 重放 `T1` 和 `T2` 之间的 write-ahead log 修改以纠正被修改的数据 4. 将最终结果存储在独立的存储位置,如 Amazon S3 我们*可以*直接在主节点或某个服务流量的副本上完成这些步骤。问题是,这项任务会消耗大量的 IOPS 和计算资源,尤其是对于大型数据库。我们的目标应该是尽可能减少备份对生产查询服务的影响。 因此,我们采取了不同的方法。我们启动一组全新的 EC2 实例,每个分片一个,在这些实例上管理备份。 这些新实例将承担备份中的大部分工作。由于我们在 AWS 和 GCP 等云中运行这些分片数据库,动态启动数十或数百个实例来短时间完成备份是可行的。这会增加少量成本,但为了尽量减少对生产的负面影响,这是值得的。 ## 复用旧备份 (https://planetscale.com/blog/massively-parallel-postgres-backups#reusing-old-backups) 下一步是将最近的备份恢复到每个分片。先前的备份存储在对象存储中。这里我们以 Amazon S3 为例,但同样适用于其他云(如 Google Cloud 中的 GCS)。这些数据直接从对象存储流式传输。 这种方法需要临时计算资源,并将每个分片的数据从对象存储导出再写回。我们接受这种成本有两个重要原因: - 只有最近的 WAL 来自主节点,从而最小化对生产的影响 - 每个周期都验证了之前的备份可以被恢复和重放 一旦所有复制完成,这 8 台服务器就拥有了 12 小时前 8 个分片各自的确切状态。 ## 重放 WAL (https://planetscale.com/blog/massively-parallel-postgres-backups#replaying-the-wal) 现在我们必须在每个分片上,将旧备份追赶至数据库的当前状态。这需要重放 Postgres Write-Ahead Log 中从 12 小时前到现在的所有更改。 一种简单的方法是从主节点直接拉取 WAL。但这有几个问题: 1. 这会产生不可忽视的生产影响。在写入频繁的数据库上重放 12 小时的 WAL 可能需要几十分钟,甚至一小时以上。 2. 因为我们不希望服务器存储被 WAL 占用过多,我们会持续将其归档到 S3。因此,主节点上很可能甚至没有完整的过去 12 小时 WAL。 在 PlanetScale,所有 Postgres 数据库都使用 [wal-g (https://wal-g.readthedocs.io/PostgreSQL/)](https://wal-g.readthedocs.io/PostgreSQL/) 来持续归档其 write-ahead log。Neki 分片数据库类似,只是每个分片有独立的 WAL 归档流。 如果我们改为从那里流式传输呢? 这几乎解决了问题。剩下的问题是,Postgres 只在段文件完整后才会归档 WAL。如果写入流量没有更快地填满段文件,我们设置的五分钟 `archive_timeout` 参数会强制切换段文件以便归档。这意味着最新的更改可能还没有到达 S3。因此,我们采用混合方法。系统使用 S3 进行大部分重放,然后直接从主节点流式传输最后约几分钟的更改。理想情况下,这最后一步只需几秒钟,而不是几分钟或几小时。 将这一步放到我们的分片数据库中: ## 完成周期 (https://planetscale.com/blog/massively-parallel-postgres-backups#completing-the-cycle) 当每个节点都追赶复制到时间 `T`(`T` 是我们记录为备份时间的时间戳)时,我们停止 WAL 复制,冻结备份的时间点。时间 `T` 会被保存,以确保我们确切知道此备份包含的精确时间(精确到秒)。完整备份现在是一致的,没有数据涂抹。最后一步是加密并将这些备份发送到新的 S3 存储桶中妥善保管。 完成后,备份节点就完成了它们的使命,并被退役。 ## 首次备份 (https://planetscale.com/blog/massively-parallel-postgres-backups#the-initial-backup) 刚才描述的周期假设我们开始时有一个 12 小时前的良好备份。在稳态下确实如此,但对于一个全新的数据库则不然。 在新数据库创建后的 12 小时内,会使用 `pg_basebackup` 为每个分片初始化一个备份节点。 `pg_basebackup` 是 PostgreSQL 内置的客户端工具,用于对整个数据库集群进行物理备份:包括数据目录、表空间以及恢复所需的配置。 为什么不直接上传 `pg_basebackup` 的输出?我们仅用它来初始化临时备份节点。持久备份使用 `wal-g` 创建,这样初始备份和稳态备份的格式和恢复流程保持一致。 首次备份的步骤与稳态类似,但略有不同: 1. 为每个分片启动一个新的 EC2 实例。 2. 在每个实例上运行 `pg_basebackup`,从其主节点复制数据。 3. 配置每个实例从其主节点进行复制。 4. 重放 WAL,直到每个实例都追上进度。 5. 在时间 `T` 停止复制。 6. 使用 `wal-g` 对每个备份进行格式化、加密并上传到 S3。 一旦完成,所有未来的备份都将按照 恢复 -> 追赶 -> 保存 的流程进行。 ## 对速度的追求 (https://planetscale.com/blog/massively-parallel-postgres-backups#need-for-speed) 我们做这么多并行处理,部分原因是分片的本质。无论是 4 个分片还是 400 个分片,由于每个分片包含自己的 Postgres 主节点,我们完全可以采用一个非常可靠的系统(常规 Postgres 备份),并在每个分片上重复执行。 这样做的一个副作用是,即使对于大型数据库,备份也非常快。 对于每次备份,数据传输步骤是: 1. 从 S3 恢复旧备份 2. 使用 S3 和主节点的混合方式追赶备份 3. 将新文件发送回 S3 想象一下,在一个*未分片*的 32 TB 数据库上完成这个备份周期需要什么。PlanetScale 上的备份在 S3 中会以静态加密的方式压缩存储。我们假设这个 32 TB 数据库的备份压缩后可达到 20 TB。因此,我们有: 1. 从 S3 传输 20,000 GB 2. 追赶(例如:20 GB,压缩后为 10 GB) 3. 传输 20,010 GB 回到 S3 这相当于总传输量约为 40,030 GB。如果我们的各种节点和互联网络能够持续保持 500 MBps 的传输速率,这意味着备份大约需要 22 小时。关键是,这意味着如果我们想要每天两次备份,多个备份会重叠。这会让我们无法满足恢复点目标(RPO)。 同一个数据库在 Neki 中,分布在 8 个各存储约 4 TB 的分片上,表现则完全不同。总的来说,我们仍然需要传输、追赶并存储相同的 40,030 GB。但如果我们能在 8 个独立的备份节点上并行完成,每个节点具备 500 MBps 的速率,我们就能将总时间减少到约 2.8 小时。 添加更多分片会更好。相同的数据分布在 32 个分片上,只需 42 分钟即可完成备份。备份速度扩展性良好。100 TB 分布在 100 个分片上的备份速度与 1 TB 在单个分片上的备份速度大致相同。 ## 备份的用途是什么? (https://planetscale.com/blog/massively-parallel-postgres-backups#what-are-backups-used-for) 备份的一个原因是数据安全。在意外数据删除或百万分之一概率的数据库灾难情况下,备份(以及 WAL)是必不可少的后备手段。 但在 PlanetScale,备份也是日常数据库操作的核心部分。特别是对于 [Metal (https://planetscale.com/metal)](https://planetscale.com/metal) 数据库(即使用本地 NVMe 的数据库),每次调整数据库大小时也会用到备份。 调整 Metal 数据库大小需要: 1. 对于分片数据库中的每个现有节点,在目标大小下启动一个全新的 EC2 实例。在一个有 8 个分片、每个分片有 1 个主节点和 2 个副本的数据库中,我们当前可能运行在 `8 x 3 = 24` 个 `i8g.xlarge` 节点上。在运行期间,我们初始化 24 个额外的 `i8g.2xlarge` 以将计算能力翻倍。 2. 24 个新节点中的每一个都从 S3 下载最近的备份副本,并通过归档的 WAL 开始追赶数据库的当前状态。 3. 所有节点被初始化为原始主节点的备用节点,并完成最终的复制追赶。 4. 当所有节点与主节点同步后,从旧的 `i8g.xlarge` 主节点切换到选定的新 `i8g.2xlarge` 主节点。 5. 较小的节点被退役,只保留较大的主节点和副本。 至此,我们完成了所有分片的完整调整大小过程。备份用于促进新节点的创建/追赶。这个过程可能需要几分钟到几小时,具体取决于备份的大小以及需要重放的 WAL 数量。 备份还在节点因意外故障需要更换时使用。如果服务器的平均寿命是 5 年,在只有一个主节点的小型数据库上,你很少会注意到故障。但对于一个有数百个分片、每个分片有多个副本的数据库,在给定的一周或一个月内发生节点故障的统计概率会显著增加。当我们看到单个节点故障(例如,某个分片中的一个副本)时,替换过程如下: 1. 初始化一个与之前故障节点相同大小新的云实例。 2. 使用前面描述的过程将最近的备份恢复到该实例,并追赶 WAL。 3. 初始化为原始主节点的从节点,同步数据。 4. 这个新节点现在可以主动服务读查询,和/或在切换或故障转移操作中作为新的主节点。 ## MySQL 呢? (https://planetscale.com/blog/massively-parallel-postgres-backups#what-about-mysql) 这里的所有内容都是关于我们如何通过 Neki 完成 Postgres 数据库的分片备份和调整大小。PlanetScale 还运营大规模分片 MySQL 数据库,由 Vitess 作为查询路由/分片层。 备份和调整这些数据库的过程非常相似!我们有一篇[专门的博客 (https://planetscale.com/blog/faster-backups-with-sharding)](https://planetscale.com/blog/faster-backups-with-sharding)来介绍其工作原理,但主要区别在于:我们使用 `VTBackup` 而不是 Postgres 的内置备份;使用 [MySQL 二进制日志 (https://dev.mysql.com/doc/refman/en/binary-log.html)](https://dev.mysql.com/doc/refman/en/binary-log.html) 复制而不是 WAL;复制追赶从主节点而不是 S3 与主节点的混合方式完成。 ## 让它变得平凡 (https://planetscale.com/blog/massively-parallel-postgres-backups#make-it-boring) 最终,我们希望所有这些对你都是透明的。进行备份应该像自动化调度或点击按钮一样简单。调整数据库大小应该是一次点击或一个 API 调用即可完成。 然而,我们也对数据库操作的细节有着深深的欣赏。通过这段备份生命周期之旅,我们希望你对数据库编排的奇妙之处有了新的认识。 如果你觉得这很吸引人,我们一直在寻找[有才华的数据库工程师 (https://planetscale.com/careers)](https://planetscale.com/careers)。

相似文章

PlanetScale 的 Neki

Hacker News Top

Neki 是由 PlanetScale 提供的分片式 Postgres 解决方案,可实现水平扩展至数亿 QPS 和 PB 级数据,且操作零停机。

扩展PostgreSQL以支持8亿ChatGPT用户

OpenAI Blog

OpenAI分享了扩展PostgreSQL以支持8亿ChatGPT用户及每秒数百万查询的技术见解,采用了单主架构搭配50个只读副本,同时通过分片和优化策略管理写入密集型工作负载带来的挑战。

让768台服务器看起来像1台

Hacker News Top

PlanetScale介绍了如何使用数据库分片将关系型数据库从单台服务器扩展到768台服务器,并解决诸如写入限制和资源争用等瓶颈问题。