我构建了一个零停机时间的数据库迁移流水线(PostgreSQL 到 Aurora)

发布: (2025年12月4日 GMT+8 11:22)
8 分钟阅读
原文: Dev.to

Source: Dev.to

问题

去年,我接手了一个运行在自托管 PostgreSQL 上的项目。数据库已经增长到 500 GB,我们在维护上花费的时间太多:打补丁、备份、复制问题,都是常见的头疼事。于是决定迁移到 Aurora PostgreSQL,以获得托管运维、更好的可扩展性以及原生的 AWS 集成。

难点在哪里?这是一套为 50,000 名每日活跃用户提供服务的生产数据库。任何停机都会导致收入损失和用户不满。业务方给我们的维护窗口是……零分钟。压力山大。

我最初的尝试(以及为什么没有成功)

我最初的想法很简单:在业务低谷期使用 pg_dumppg_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 监控 实现全方位可观测性

架构概览

Architecture Overview

蓝绿策略的工作方式如下: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:

MetricValue
已迁移数据总量512 GB
迁移时间(全量加载)3 小时 22 分钟
切换期间 CDC 延迟2.1 秒
应用停机时间0 秒
数据验证错误0

Post‑migration, we observed:

  • 40 % 降低读取延迟(Aurora 只读副本)
  • 数据库维护时间为零
  • 自动备份和时间点恢复

Lessons Learned

  1. 彻底测试你的验证脚本。 我最初有一个 bug,checksum 查询没有正确处理 NULL 值。幸好在预演环境中捕获到了。
  2. 适当为 DMS 实例配置规模。 我们最初使用 dms.r5.2xlarge,在全量加载时遇到 CPU 限制。升级到 4xlarge 后迁移时间减半。
  3. 极度关注 CDC 延迟。 我为超过 30 秒的延迟设置了 CloudWatch 警报。在迁移过程中,当有人在源端运行批处理作业时,延迟一度飙升至 45 秒。及时获知后我们得以推迟切换,直至延迟恢复。
  4. 制定并实际测试回滚方案。 我们在切换后让源 PostgreSQL 继续运行 48 小时。当出现一个小 bug(与迁移无关)时,能够回滚的选项让大家在调查期间安心。
  5. 沟通要比你想象的更频繁。 我们每小时发送一次更新。
Back to Blog

相关文章

阅读更多 »