一次数据迁移复盘:边界、口径与回滚
迁移本身两天就跑完了,真正花时间的是迁移前后的口径对齐。这篇复盘写我们怎样划清迁移边界、如何用小样本双向核对、回滚方案为什么必须在上线前演练一次。
一、背景:表不多,映射复杂
这是一次从旧系统到新系统的数据迁移。表不算多,但字段映射复杂:同一类信息在旧系统里散在几张表,新系统合并成一张;历史数据跨了几年,中间还改过几次规则。
排期时,开发估的是导入脚本的工作量,两天左右能跑完。这个估算没有错,脚本确实两天就跑通了。真正花时间的是前后口径的对齐,以及上线那天的两个意外。
二、边界:先决定「不迁什么」
迁移开始前,我们做了一件看起来浪费时间的事:把边界写下来。哪些表迁、哪些表不迁;哪些年份迁、哪些年份只归档;哪些状态的数据不迁,例如测试数据、已作废但仍有引用的单据、重复导入产生的副本。
写边界的过程比想象中吵。使用方希望全都迁过来,理由是空间够;但全迁意味着每个历史状态都要在新系统里找到对应,工作量翻几倍,而且很多状态在新系统里根本不存在。
最后我们定了一条规则:不迁的部分要写下来,写明原因和不迁之后的查看方式,并且发给使用方确认。不迁不是一个纯技术决定,它会影响别人怎么查旧数据,所以必须让使用方知道,而不是埋在文档里。
三、口径差异:五处最容易出事
真正麻烦的是同一个词在两边的意思不一样。我们整理出五处。
- 状态码。旧系统的「已完成」包含「已关闭」和「已作废」,新系统把它们分开了。直接映射会把作废数据变成正常数据。
- 时间口径。旧系统有些时间存的是本地时间,有些带时区;新系统统一要求带时区。这一处后来在上线当天出了问题。
- 空值与零。旧系统用 0 表示「未填写」,新系统用空值表示。直接搬过去,统计里会多出一批金额为 0 的记录。
- 金额单位。有的表存元,有的表存分,字段名几乎一样,只看名字看不出来。
- 编码。旧系统的人员编码和新系统不是一套,需要一张映射表,表里还有合并、离职、重名的情况。
这五处没有一处是技术难点,每一处都是「有没有人认真对过」。
四、核对:约 1% 的小样本,双向对
我们的核对方法比较笨:从每个关键表里抽大约 1% 的样本,新旧两边分别导出,逐字段对比。
差异分两类:可解释的和不可解释的。可解释的是口径差异造成的,例如空值变零、状态拆分,我们要在对照表里写明转换规则;不可解释的是没想明白的,例如某条记录旧系统有、新系统找不到,或者金额差了几块钱。
规则很简单:不可解释的差异必须清零,才能继续往下走;可解释的差异要有书面说明,不能靠一句「我知道为什么」口头过关。
1% 不算多,但它足以暴露系统性错误。我们后来发现的问题,几乎都是抽样时看到的模式,而不是某一条孤立的记录。
五、回滚:上线前必须演练一次
回滚方案写起来很容易:旧系统保留只读,出问题就切回去。但「切回去」这三个字包含了很多没写的动作。
我们的做法是:切换窗口定在业务低峰,旧系统在上线后一段时间内保持只读可查;切换前把回滚步骤写成清单,包括停哪些服务、改哪个配置、通知谁、怎么确认数据没有双写。
最重要的是上线前演练一次。我们找了一个非生产环境,按清单完整走了一遍,记录每一步的实际耗时和遇到的问题。演练暴露了两处:某一步需要人工确认,但没写负责人;回滚之后有一个缓存没清,页面显示的还是新数据。这两处在真正的回滚里都会变成事故。
六、上线当天的两个问题
即便做了上面这些,上线当天还是出了两个问题。
第一个是漏建索引。导入脚本在小样本上跑得很快,全量导入时慢到超出窗口。排查后发现,有张关联表少建了一个索引。小样本看不出这个问题,因为数据量小的时候,全表扫描也很快。
第二个是时区。部分时间字段在导入时被当作带时区的时间写入,导致一部分记录的时间偏移了一天。发现得不算晚,但修正需要重新导入那批数据,还要通知使用方当天报表的口径变化。
两个问题都不在迁移逻辑里,而在迁移环境和数据本身里。这也是我后来坚持要在接近真实数据量的环境上试跑一次的原因。
七、可以复用的清单
- 迁移边界写成一页纸:迁什么、不迁什么、不迁的数据怎么查,发给使用方确认。
- 列出同一个词在两边的不同含义,至少覆盖状态、时间、空值、单位、编码五类。
- 准备一张字段映射表,每个字段写明来源、转换规则和负责人。
- 抽 1% 左右的小样本双向核对,差异分成「可解释」和「不可解释」,不可解释的必须清零。
- 回滚步骤写成清单,上线前在非生产环境完整演练一次,记录耗时和卡点。
- 用接近真实数据量的环境试跑一次导入,重点看索引、批处理大小和超时设置。
八、结尾
这次迁移给我的最大改变,是我不再把迁移当成一个导入脚本的任务。它更像一次口径谈判:技术只占一小部分,大部分时间在和别人确认「这个词你到底是什么意思」。
两天跑完的脚本,几周对齐的口径。如果重来一次,我还是会先花那几周,因为返工的成本比提前对齐高得多。
本文为个人经验与观点,不代表任何机构。