本页是译文,以英文原文为准。 阅读原文
写作50 套生产库迁到 Aurora,停机不到 3 分钟
支撑 50+ 套生产 MySQL 迁到 Aurora 的方法论 —— 停机预算是设计输入,不是事后才量出来的数字。
大约两年时间,我们把约 50 套生产库从自建 MySQL 迁到 AWS Aurora。最大的几套是数 TB 的 tier-0 系统, 7×24 写入。多数切换停机压在 3 分钟以内。
这个数字不靠维护窗口里小心点几下。它来自把停机预算当作 设计输入:一旦「3 分钟」 变成硬性要求,而不是一句期望,后面每个决定 —— 复制拓扑、演练门禁、连接排空、DNS 权重 — — 都被逼出一种具体形态。这篇写的就是这条约束逼出来的方法论,外加路上真正咬过我们的那几口。
先交代一个前提,它决定了后面全部内容:迁一套库是项目,迁五十套是平台。五十套库分属五十个团队, 一次性迁移方案套不上去。所以工作单元从来不是「一次迁移」,而是一条参数化流水线, 每个 workload 各自调参。
核心思路:不迁数据,做复制
关键就一句:切换那天,几乎不该剩下活。
提前把复制从自建接到 Aurora,让它在切换日前很久就追平,切换本身缩成一次短促的机械操作:
- 停止写入源库
- 确认复制追平(lag = 0)
- 把 Aurora 提升为主库
- 把流量切过去
- 恢复业务运行
总停机时间就是验证、提升、切流量这三步 —— 通常压在 3 分钟以内,因为每个慢步骤都在几天甚至几周前做完了。
流水线
阶段 1:评估(单库提前数周,整体提前数月)
容量规划
- 分析查询模式和峰值负载
- 先按写入 workload 定 Aurora 实例规格 —— writer 的规格由你自己选, reader 自动扩缩再怎么加, 也救不了一个规格偏小的 writer
- 用 Aurora Auto Scaling 规划读流量的突发容量 —— 它只增减 reader 实例,对 writer 一点用没有
兼容性测试
- 用应用测试套件对着 Aurora 跑
- 找出 SQL 语法差异(比你想象的少)
- 用生产数据、不只是生产 schema 来测字符集和排序规则(这条咬过我们一次 —— 下面细说)
基础设施准备
- 用 Terraform 开通 Aurora 集群
- 配置安全组和网络
- 在你需要之前就把监控面板建好
阶段 2:数据迁移
初始同步。 初始的 full-load 任务用 AWS DMS,持续同步走 MySQL 原生 binlog 复制。
关于 DMS 说句实话,迁移指南通常把这段粉饰过去:DMS 能干活,但别指望设好就不管。库大、 源库写入重,full-load 任务就得盯着。LOB 怎么处理、任务怎么配,比控制台向导暗示的重要得多。 CDC 还要求源库 binlog 保留时间够长,能扛过你最慢的那一天。我们只把 DMS 当全量装载的引导器。 切换时真正必须可信的那一段交给原生复制 —— 那部分我们能直接查看、度量、推理。
复制延迟管理。 数 TB 的复制要花时间。我们盯三样:复制延迟(落后主库多少秒)、 binlog 位点、Aurora 集群磁盘使用率。
阶段 3:验证
从不相信,永远验证:
- 源库与目标库行数一致
- 关键表做校验和比对
- 对着 Aurora 副本跑读流量
- 用合成事务验证端到端链路
切换前,我们把读流量打到 Aurora(当时还是副本)上,跑了数周,占比逐步提高。
阶段 4:切换 —— 靠演练,不靠英雄主义
每次切换都先对着还原副本跑一遍,脚本、检查项、回滚点和生产一模一样。演练是一道门, 不是一句建议:没练到无聊,就别想排上生产日期。
生产用的 runbook:
切换前(T-60 分钟)
- 确认复制延迟 < 1 秒
- 确认回滚路径已验证
- 通知相关方
- 静默期 —— 不发版
切换(T-0)
- 停止应用写入(优雅停止)
- 确认复制追平(lag = 0)
- 把 Aurora 提升为主库
- 排空连接、调整 DNS 权重 —— 自动执行并计时,因为手工「切流量」正是 3 分钟预算死掉的地方
- 恢复应用流量
切换后(T+30 分钟)
- 监控错误率
- 确认事务成功率
- 确认没有向旧主库的复制
- 更新文档
阶段 5:清理
在 Aurora 上跑满一周、旧主库留着兜底之后:下线旧主库、更新灾难恢复文档、做一次复盘。 复盘喂给下一次迁移 —— runbook 是活的,不是一次性产物。
连接惊群问题
这段值得单开一节,因为这种故障模式只在平台规模上才存在。
把流量切到新 endpoint 时,所有应用会同时重连。五十个连接池一起扑向刚提升上来的 writer,就能在一次教科书级切换的正中间,造出一场自己给自己的故障 —— 数据库没事,迁移「完成」了,站点照样不可用。
我们在路由层把它吸收掉了:应用不直连数据库 endpoint,持的是到 Vitess VTGate 的长连接。重定向发生在代理层,应用根本不用集体重连。没有代理层,穷人版做法是分批重启加带抖动的重连。但真正的教训是:切换方案里没写重连方案,就不算完整 —— 这个决定属于平台,不属于各团队自己的迁移检查清单。
我们学到了什么
管用的东西
1. 极其详尽的文档。 我们维护一份 21 页的迁移指南,每次迁完就更新。出了意外, 记下来。到第 50 套时,意外已经很少了。
2. 反复演练。 每个迁移步骤都先在 QA 里验证过。不是「我们跑过一次」—— 整套切换流程反复跑到无聊为止。
3. 回滚测试。 回滚路径要跟正向路径测到一样细。我们真回滚过一次生产切换 —— 切换完才发现的应用问题 — — 很顺,就因为练过。停机预算里必须留出回头路。
4. 过度沟通。 相关方清楚知道迁移什么时候做、会遇到什么、出问题找谁。切换期间每 15 分钟同步一次。
咬到我们的东西
1. 字符集边界情况。 Aurora 对字符集比我们那套老 MySQL 更严格。有些带遗留数据的表要小心处理。 教训:用生产数据测,别只用生产 schema。
2. 时区表。 我们的 Aurora 建出来时没带 MySQL 时区数据。依赖 CONVERT_TZ() 的应用一直失败,
直到我们把它加载进去。现在这一步进了迁移前检查清单。
3. 连接池惊群。 前面讲过 —— 三个里最像平台问题的一个。
基础设施
全部通过 Terraform 开通:
module "aurora_mysql" {
source = "path/to/aurora-module"
cluster_name = "critical-database"
engine_version = "8.0.mysql_aurora.3.04.0"
# Production-ready defaults
instance_class = "db.r6g.2xlarge"
instances = 3 # Writer + 2 readers
# Built-in monitoring (Database Insights)
database_insights_enabled = true
cloudwatch_alarms_enabled = true
}
这个模块把多年教训编进了默认值:开通时加载时区数据、监控默认打开、reader 规格建议直接内置。第五十一个来迁移的团队,不用再重新发现前五十个踩过的坑。真正让它成为平台的是这一点,而不是哪一份 runbook:伤疤都长在模块里。
版本说句实话:上面那个引擎版本是钉死的。自建 MySQL 和 Aurora 之间有版本差异,这是我们真正踩到过的兼容性变量之一。钉版本要刻意,升级也要刻意。
迁移指南里没人写的那笔代价
Aurora 对我们一直非常稳,但它不是免费升级。有两笔成本值得点名。
Aurora 对 I/O 收费。 账单项按每百万次请求计费。写入量大的 workload 上,从自建迁过来的人看了可能真会一惊 — — 自建那边 IOPS 是沉没的硬件成本。对我们大多数库不痛不痒;对最话痨的几套写重型库, 它实打实改了成本模型。迁之前先建模,别等第一张账单。
你买的是 Aurora 的运维模型。 托管故障转移、存储自动扩缩、按时间点恢复都很好 — — 可它们也是一条边界。引擎层能深调的选项变少,你手里只剩实例规格、参数组和架构这几根杠杆。 如果你的 workload 依赖冷门的 MySQL 配置,迁移前就确认它能活着穿过这条托管边界,别等迁移中才发现。
这两条都不会改变我们的决定,但可能改变别人的决定,所以迁移指南里应该写上。
数字
两年迁了大约 50 套库。tier-0 系统平均切换停机压在 3 分钟以内。一次回滚,起因是切换后才发现的应用问题 —— 回滚本身很顺畅,因为那条路我们测过。零数据丢失事故。
结论
对我们来说,难的不是「怎么把一套库迁到 Aurora」,而是怎么让第五十次迁移, 做到第一次本该做到的那种安全。我们的答案:停机预算是设计输入, 切换练到无聊为止,重连方案放在平台层,每条教训都回编进模块 —— 下一个团队从我们停下的地方起步。
到后来,多数迁移都平平淡淡地完成了,这正是我们想要的:让稳妥的做法成为平台的默认做法。
这篇文章背后的平台背景:Aurora 迁移工具链。