我构建了一个零停机时间的数据库迁移流水线(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。它负责繁重的工作:在托管服务中完成全量加载和变更数据捕获。挑战在于围绕它构建一套可重复且安全的迁移流程。
解决方案
我构建了一个完整的迁移框架,包含四个主要组件:
- 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 验证工具,检查多个维度:
# 快速验证(使用表统计进行快速估算)
python validation.py --quick
# 完整验证(精确计数、校验和、序列值)
python validation.py --full
# 仅检查 DMS 状态
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 Integration
所有操作均通过 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 通知。
结果
The migration completed successfully with these metrics:
| Metric | Value |
|---|---|
| 已迁移数据总量 | 512 GB |
| 迁移时间(全量加载) | 3 小时 22 分钟 |
| 切换期间 CDC 延迟 | 2.1 秒 |
| 应用停机时间 | 0 秒 |
| 数据验证错误 | 0 |
Post‑migration, we observed:
- 40 % 降低读取延迟(Aurora 只读副本)
- 数据库维护时间为零
- 自动备份和时间点恢复
Lessons Learned
- 彻底测试你的验证脚本。 我最初有一个 bug,checksum 查询没有正确处理
NULL值。幸好在预演环境中捕获到了。 - 适当为 DMS 实例配置规模。 我们最初使用
dms.r5.2xlarge,在全量加载时遇到 CPU 限制。升级到4xlarge后迁移时间减半。 - 极度关注 CDC 延迟。 我为超过 30 秒的延迟设置了 CloudWatch 警报。在迁移过程中,当有人在源端运行批处理作业时,延迟一度飙升至 45 秒。及时获知后我们得以推迟切换,直至延迟恢复。
- 制定并实际测试回滚方案。 我们在切换后让源 PostgreSQL 继续运行 48 小时。当出现一个小 bug(与迁移无关)时,能够回滚的选项让大家在调查期间安心。
- 沟通要比你想象的更频繁。 我们每小时发送一次更新。