我构建了零停机时间的数据库迁移流水线(PostgreSQL 到 Aurora)
Source: Dev.to
问题
去年,我接手了一个运行在自托管 PostgreSQL 上的项目。数据库已经膨胀到 500 GB,维护工作(打补丁、备份、复制问题等)耗时太多。于是决定迁移到 Aurora PostgreSQL,以获得托管运维、更好的可扩展性以及原生的 AWS 集成。
难点在于:这是一套服务于 50,000 名日活用户的生产数据库。任何停机都会导致收入损失和客户不满。业务方给我们的维护窗口是……零分钟。压力山大。
我最初的尝试(以及为什么没成功)
我最初的想法很简单:在业务低谷期使用 pg_dump 和 pg_restore。经典做法,对吧?
# The naive approach
pg_dump -Fc production_db > backup.dump
pg_restore -d aurora_target backup.dump
对于 500 GB 的数据库,这大约需要 4–6 小时,取决于网络和实例规格。这意味着在这 4–6 小时内,源库会不断产生新数据,数据会变得陈旧,无法接受。不可接受。
我也尝试了 PostgreSQL 原生的逻辑复制,但在跨 AWS 账户设置发布/订阅并做好安全控制的过程像是一个巨兽。再加上相关运维工具几乎都是自己动手实现的。
随后我发现了 AWS DMS。它可以在托管服务中完成全量加载和变更数据捕获(CDC)。挑战在于围绕它构建一个可重复、安全的迁移流程。
解决方案
我构建了一个完整的迁移框架,包含四大组件:
- Terraform 模块:用于所有 AWS 基础设施
- Python 自动化脚本:用于验证和切换
- GitHub Actions 工作流:实现 CI/CD
- CloudWatch 监控:提供全方位可观测性
架构概览

蓝绿策略的工作原理如下:DMS 先完成全量加载,然后切换到 CDC 模式捕获持续变更。两个数据库保持同步,直到我们准备好切换为止。
Terraform 基础设施
我使用模块化的 Terraform 来实现跨环境的可复用。下面是 DMS 模块的示例:
module "dms" {
source = "./modules/dms"
project = "db-migration"
environment = "prod"
subnet_ids = var.private_subnet_ids
security_group_ids = [module.networking.dms_security_group_id]
replication_instance_class = "dms.r5.4xlarge"
multi_az = true
source_db_host = var.source_db_host
source_db_username = var.source_db_username
source_db_password = var.source_db_password
target_db_host = module.aurora.cluster_endpoint
target_db_username = var.aurora_master_username
target_db_password = var.aurora_master_password
}
DMS 模块会创建:
- 具备合适规格的复制实例
- 带有正确 SSL 配置的源端点和目标端点
- 启用 CDC 的复制任务
- 用于监控延迟和错误的 CloudWatch 告警
验证脚本
在任何切换之前,都需要确认数据一致性。我编写了一个 Python 验证工具,支持多维度检查:
# Quick validation (uses table statistics for fast estimates)
python validation.py --quick
# Full validation (exact counts, checksums, sequence values)
python validation.py --full
# Just check DMS status
python validation.py --dms
完整验证会执行以下检查:
| 检查项 | 描述 |
|---|---|
| 行数统计 | 对比源库和目标库的精确行数 |
| 校验和 | 对每个表的抽样数据计算 MD5 哈希 |
| 序列 | 验证序列值是否同步 |
| 主键 | 确保所有表都有主键(CDC 必须) |
| DMS 状态 | 任务运行状态、复制延迟是否低于阈值 |
校验和验证代码片段:
def calculate_checksum(self, table: str, columns: list, limit: int = 1000) -> str:
"""Calculate MD5 checksum of sample rows."""
cols = ", ".join(columns)
query = f"""
SELECT md5(string_agg(row_hash, '' ORDER BY row_hash))
FROM (
SELECT md5(ROW({cols})::text) as row_hash
FROM {table}
ORDER BY {columns[0]}
LIMIT {limit}
) t
"""
result = self.execute_query(query)
return result[0][0] if result else None
切换流程
切换是最让人紧张的环节。我构建了一个多阶段流程,并在每个阶段提供自动回滚能力:
| 阶段 | 操作 | 是否支持回滚 |
|---|---|---|
| 1 | 预验证(检查 DMS、行数) | 是 |
| 2 | 等待同步(CDC 延迟低于阈值) | 是 |
| 3 | 排空连接(终止源库连接) | 是 |
| 4 | 最终同步(等待剩余变更) | 是 |
| 5 | 停止复制 | 仅手动 |
| 6 | 后置验证 | 仅手动 |
切换脚本在每个阶段后会将状态保存为 JSON,若出现故障可以从中断点恢复:
# Always do a dry run first
python cutover.py --dry-run
# Execute when ready
python cutover.py --execute
# Resume from saved state if interrupted
python cutover.py --execute --resume
GitHub Actions 集成
整个过程通过 GitHub Actions 自动化。生产环境的切换工作流需要手动批准:
jobs:
approval:
name: Approve Cutover
runs-on: ubuntu-latest
if: github.event.inputs.mode == 'execute'
environment: prod-cutover # Requires manual approval
steps:
- name: Cutover Approved
run: echo "Cutover approved"
cutover:
name: Database Cutover
needs: [approval]
# ... actual cutover steps
工作流会从 AWS Secrets Manager 拉取凭证,执行切换脚本,上传状态制品用于审计,并在失败时发送 SNS 通知。
结果
迁移成功完成,关键指标如下:
| 指标 | 数值 |
|---|---|
| 总迁移数据量 | 512 GB |
| 全量加载耗时 | 3 小时 22 分钟 |
| 切换期间 CDC 延迟 | 2.1 秒 |
| 应用停机时间 | 0 秒 |
| 数据验证错误 | 0 |
迁移后我们观察到:
- 读取延迟下降 40 %(Aurora 只读副本)
- 数据库维护时间为零
- 自动备份与时间点恢复(PITR)
经验教训
- 彻底测试验证脚本。 最初我在校验和查询中没有正确处理
NULL,幸好在预演环境捕获到了。 - 为 DMS 实例合理选型。 我们最初使用
dms.r5.2xlarge,全量加载时 CPU 达到上限。升级到4xlarge后迁移时间减半。 - 严密监控 CDC 延迟。 我为延迟超过 30 秒设置了 CloudWatch 告警。迁移过程中一次批处理导致延迟飙至 45 秒,及时发现让我们推迟切换,避免风险。
- 拥有并演练回滚方案。 我们在切换后保留源 PostgreSQL 运行 48 小时。出现与迁移无关的轻微 bug 时,能够快速回滚,给团队带来安心。
- 沟通要比想象的多。 我们每小时发送一次进度更新。