当 Postgres 数据增长到 PB 级,并被拆分到大量分片后,备份不再是一条 pg_dump 命令能够解决的问题。PlanetScale 的实践表明,关键不是让单个备份任务跑得更快,而是把分片读取、对象存储上传和恢复校验拆成可水平扩展的并行流水线,再通过 WAL 重放把数据库推进到目标时间点。
瓶颈不只在数据库读取
大型备份通常同时消耗三类资源:数据库磁盘读取带宽、备份节点的 CPU 与网络带宽,以及对象存储的写入吞吐。只提高某一项,往往只是把拥塞转移到下一环。
分片架构提供了天然的并行边界。不同分片可以由独立 worker 处理,每个 worker 负责读取快照、压缩或分块、计算校验和,并把结果上传到对象存储。调度器则控制全局并发度,避免数百个任务同时打满生产数据库。
这类系统通常需要区分两层并行:
- 跨分片并行:多个数据库分片同时备份,决定整体吞吐上限。
- 分片内并行:单个分片内部并行导出表或复制数据文件,用来充分利用单机资源。
- 分块上传:大文件切成独立对象或使用 multipart upload,降低单连接故障的重试成本。
- 受控限流:根据副本延迟、磁盘队列和网络利用率动态减少 worker 数量。
并发并非越高越好。备份 worker 会与线上查询争夺缓存、I/O 和网络,因此应优先从只读副本读取,并给调度器设置明确的数据库级并发上限。
基础快照与 WAL 各自解决什么问题
基础快照提供某个时刻的数据主体,WAL 则记录此后发生的变更。恢复时先装载基础快照,再按顺序重放 WAL,便可以把实例推进到指定时间或一致性位置。这也是时间点恢复(PITR)的基础。
对象存储适合保存这两类数据:基础快照体积大但生成频率较低,WAL 文件较小却持续产生。二者应使用不同的目录、保留周期和生命周期规则。例如,近期 WAL 可以保留在标准存储层,过期基础快照再转入低频存储。
分片系统的难点在于“一致性”不再只属于一台 Postgres。每个分片都能获得自己的 LSN 和时间戳,但这些位置未必构成业务上的全局一致点。如果应用存在跨分片事务、异步事件或全局元数据,备份清单还需要记录协调信息。仅仅选择时间最接近的一组快照,并不能自动保证一致恢复。
可以这样实践:并行导出多个分片
下面是一个可运行并改造的最小示例。它使用 PostgreSQL 目录格式并行导出每个分片,再通过 AWS CLI 上传到兼容 S3 API 的对象存储。运行前需要安装 pg_dump、GNU parallel 和 AWS CLI,并把 shards.txt 中的连接串替换为只读副本地址。
cat > shards.txt <<'EOF'
shard-001|postgresql://backup:password@replica-001:5432/app
shard-002|postgresql://backup:password@replica-002:5432/app
shard-003|postgresql://backup:password@replica-003:5432/app
EOF
cat > backup-shard.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
name="$1"
url="$2"
backup_id="${BACKUP_ID:?BACKUP_ID is required}"
out="backups/${backup_id}/${name}"
mkdir -p "$out"
pg_dump \
--dbname="$url" \
--format=directory \
--jobs="${PG_JOBS_PER_SHARD:-4}" \
--no-owner \
--file="$out/dump"
find "$out/dump" -type f -print0 \
| sort -z \
| xargs -0 sha256sum > "$out/SHA256SUMS"
aws s3 sync "$out" "${BACKUP_BUCKET:?BACKUP_BUCKET is required}/${backup_id}/${name}/" \
--only-show-errors
EOF
chmod +x backup-shard.sh
export BACKUP_ID="$(date -u +%Y%m%dT%H%M%SZ)"
export BACKUP_BUCKET="s3://company-postgres-backups"
export PG_JOBS_PER_SHARD=4
parallel --colsep '\|' --jobs 3 ./backup-shard.sh {1} {2} :::: shards.txt
这里有两个独立的并发旋钮:--jobs 3 控制同时备份多少个分片,PG_JOBS_PER_SHARD=4 控制每个 pg_dump 内部的并发。上线前应从较小值开始,用副本延迟和磁盘利用率确定上限。
该示例是逻辑备份,并不等同于 PlanetScale 在 PB 级系统中使用的具体实现。超大数据库通常需要更接近物理文件复制的方案,并持续归档 WAL;但这个脚本可以用于验证任务编排、对象命名、校验和与失败重试等外围机制。
用清单让备份成为可验证的数据集
上传成功不能证明备份可恢复。可以为每次备份生成一份清单,记录分片、对象位置、校验和、Postgres 版本、开始与结束时间,以及与 WAL 关联的位置。下面的 YAML 是一个可改造的清单示例,其中 LSN 应由实际备份流程采集,而不是手工填写。
backup_id: 20250308T020000Z
postgres_major_version: 16
storage_prefix: s3://company-postgres-backups/20250308T020000Z/
shards:
- name: shard-001
base_backup: shard-001/base.tar.zst
start_lsn: "0/6A000028"
end_lsn: "0/6F0000D8"
checksum_file: shard-001/SHA256SUMS
- name: shard-002
base_backup: shard-002/base.tar.zst
start_lsn: "0/51000028"
end_lsn: "0/560000D8"
checksum_file: shard-002/SHA256SUMS
wal:
prefix: s3://company-postgres-wal/
restore_target_time: "2025-03-08T02:00:00Z"
恢复程序应先验证清单完整性和对象校验和,再并行下载各分片的基础快照。数据库启动后,各分片分别重放连续 WAL;缺少任何一个必要 WAL 段都应让恢复立即失败,而不是静默启动到较早状态。
真正的验收标准是恢复
建设并行备份系统时,可以用下面的检查项约束方案:
- 为生产读取、压缩和对象存储上传分别设置并发及带宽上限。
- 将基础快照、WAL、清单和校验和作为一个不可分割的备份集合管理。
- 对对象使用不可变策略、服务端加密和独立凭据,降低误删与凭据泄露风险。
- 明确定义分片间的一致性语义,尤其是跨分片事务和全局元数据。
- 定期在隔离环境执行全量恢复,并校验表数量、关键业务约束和目标恢复时间。
- 同时记录 RPO、RTO、备份耗时和恢复耗时;备份在几小时内完成,并不意味着恢复也同样快。
PB 级备份的核心是并行化,但可靠性来自可追踪的边界:每个分片备份到了哪里、依赖哪些 WAL、对象是否完整、恢复后是否满足业务一致性。只有这些问题都能由自动化流程回答,备份才不只是对象存储里的一批文件。