29.4 在线迁移状态机
在线迁移不是一条命令,而是一个有进入条件、退出证据和失败转移的状态机。把 runbook 写成“先全量,再增量,最后切流”,现场仍会争论:什么叫追平、何时禁止旧库写入、目标 写过以后还能否回退。
本节把这些模糊动词改成可观测状态。每次转移都要求证据;证据不足就留在原状态,不用 截止时间替代正确性判断。
29.4.1 预检查、全量、增量、追平与冻结窗口
先定义状态,再填命令
| 状态 | 进入条件 | 退出证据 | 失败时动作 |
|---|---|---|---|
PREFLIGHT |
迁移合同、owner、窗口已批准 | 兼容性、容量、权限、网络和回退演练通过 | 修合同,不创建长寿命 slot |
BASELINE |
snapshot/slot 边界已建立 | 全量 manifest、reject ledger、对象验证通过 | 停装载,保留输入与证据后重建目标 |
STREAMING |
基线与增量无缝衔接 | subscription/consumer 稳定,目标持续跟随 | 修 apply/connector,不切流 |
CATCHUP |
进入切换前观察 | marker 已在目标可见,lag 和 retained WAL 入预算 | 降写入、扩容或推迟窗口 |
FROZEN |
写围栏已生效 | 旧端写测试失败,最终 marker 和强校验通过 | 解除围栏或转前滚修复 |
CUTOVER |
路由变更已批准 | 新连接身份正确,目标写 canary 成功 | 按回退矩阵决策 |
OBSERVE |
目标承接生产流量 | SLO、数据、任务、slot、日志持续合格 | 回退或前滚 |
EXITED |
观察窗口和退出条件满足 | 迁移签收、资产和凭据收尾完成 | 不再把旧源当即时回退方案 |
状态不允许跳转,例如没有 FROZEN -> final validation,不能从仍在双写的
STREAMING 直接宣布 CUTOVER。
preflight 要检查迁移语义,不只检查端口
预检查至少覆盖:
“目标能连通”只证明网络路径存在。比如目标缺少 collation、sequence 没有同步、源表无 replica identity,都可能在增量或切流阶段才暴露。
Pigsty 的迁移任务可以生成环境检查、schema、publication/subscription、进度、差异和 sequence 操作的上下文与脚本;它不能替业务确认 trigger 语义,也不知道应用路由和 外部副作用。生成脚本应进入评审和版本控制,不能把“生成成功”当作“迁移完成”。
“追平”要有业务可见 marker
单看:
只能看到 publisher 与 consumer acknowledgement 的位置关系。它不必然证明目标业务 查询已经看见某一笔事务,也不覆盖下游索引、缓存和异步任务。
更可靠的追平协议是:
- 在源端业务表或专用控制表提交唯一
migration_marker; - 记录该事务的业务 ID、提交时间和附近 LSN;
- 等目标端通过普通应用路径读到 marker;
- 同时确认 subscription worker、table state、slot、错误统计和 retained WAL 正常;
- 在一段稳定窗口中重复,而不是只采一个瞬时零延迟。
正式实验同步完 500 个 insert、200 个 update、100 个 delete 后,等待 marker 在目标端 可见,再比较两端 manifest。这个证据比“延迟图降到 0”更接近切换要求。
冻结窗口要证明旧写入真的失败
冻结不等于在群里发一句“请勿写库”。写围栏可以来自:
- 撤销专用 runtime role 的 DML 权限;
- 将旧端业务入口切换为只读;
- 应用 feature flag 阻止写请求;
- 停止 scheduler、ETL、CDC 回写和运维脚本;
- 对无法配合的 writer 建立数据库级拒绝规则。
然后用旧应用凭据执行负向 canary,确认 INSERT/UPDATE/DELETE 失败,同时需要的
SELECT 仍可用于核对。表 owner、superuser、SECURITY DEFINER 函数和绕过业务入口
的后台任务必须单独盘点;仅 revoke 普通角色无法约束这些路径。
29.4.2 影子读、双读、切流和观察
影子读与双读解决的是“应用是否认同”
数据库摘要相同,不代表应用行为相同。目标端可能因为 collation、timezone、扩展版本、 查询计划或 session 参数给出不同结果。切流前可以逐级放量:
| 方法 | 主结果来自 | 目标端副作用 | 适合发现 |
|---|---|---|---|
| 离线 replay | 源端录制流量 | 禁止 | SQL/类型/性能不兼容 |
| 影子读 | 源端 | 严格禁止 | 结果、错误码、延迟差异 |
| 采样双读 | 源端,后台比较目标 | 只读 | 长尾和真实参数差异 |
| 小比例真实读 | 目标端 | 应用正常读副作用需评估 | 连接池、缓存、SLO |
“读”也可能有副作用:SELECT nextval(...)、advisory lock、临时表、审计函数、缓存
填充、SELECT ... FOR UPDATE 都不适合直接镜像。影子层必须有 SQL allowlist、超时、
并发限制和结果脱敏;不能为验证目标把源端峰值流量翻倍。
结果比较应按业务语义规范化:
同时比较错误类别、行数、关键字段、P50/P95/P99 与资源消耗。只比较 HTTP 200 会漏掉 返回空集、排序变化和悄悄截断。
切流要拆开连接地址与数据权威
应用可能经过:
迁移 runbook 必须指出实际控制点及缓存时间。改变 DNS 不会自动清掉旧 PgBouncer 连接;改变 HAProxy backend 也不会让应用已持有的 session 消失。切换步骤通常包括:
- 记录旧 route generation、目标 endpoint 和回退 endpoint;
- 降低或确认 TTL,准备 health check;
- 在源端建立写围栏并做最终 marker/校验;
- 刷新 sequence,保证目标下一个值高于已迁移最大值;
- 修改唯一的权威路由控制点;
- drain/重建旧池,拒绝新连接进入源端写服务;
- 新连接查询
system_identifier、database、server address 与只读状态; - 执行目标 canary 并从应用层回读;
- 进入限时观察,不立即拆源。
本章实验刻意不修改真实 Pigsty 路由,只在私有证据中模拟
source -> target -> source。这证明状态机与回退逻辑,不声称实验真的改过平台入口。
生产切流必须由应用或网络 owner 执行并给出实际路由证据。
观察窗口看四层信号
| 层 | 关键证据 |
|---|---|
| 应用 | 成功率、错误分类、业务转化、队列积压、关键任务 |
| 连接/路由 | 新旧连接数、endpoint 身份、池等待、事务/会话模式 |
| PostgreSQL | TPS、延迟、锁、WAL、checkpoint、autovacuum、复制/订阅错误 |
| 数据 | canary、分桶摘要、业务不变量、target-only writes、reconciliation |
应预先写出阈值和观察时间,例如“连续 60 分钟错误率不高于基线 + 0.1%,关键不变量 为零,异常桶为零”。现场再决定“看起来还行”无法形成一致决策。
29.4.3 回退点、前滚点与不可逆动作
回退不是把连接串改回去
切流前,源是唯一 writer,回退通常只是解除源围栏并放弃目标。目标开始承接写入后, 状态发生根本变化:
若没有 reverse CDC 或显式 reconciliation,源端不知道 T1..Tn,直接回路由会造成
已成功请求消失。应在切换前确定矩阵:
| 所在阶段 | 目标端是否有独占写 | 默认策略 |
|---|---|---|
| baseline / streaming / frozen | 否 | 安全取消,恢复源写 |
| cutover,canary 尚未产生业务事实 | 否 | 快速回退 |
| cutover,只有可识别 canary | 是,可枚举 | 对账并回放 canary 后回退 |
| observe,已有普通业务写 | 是 | reverse sync/reconciliation,或前滚修复 |
| 已执行破坏性 schema/源退役 | 是且难逆 | 按灾难恢复或专门回迁方案处理 |
正式实验在目标写入唯一 canary,得到 sequence 值 900001。模拟回退前,证据包先识别
并把这条目标独占数据对账到源,再在源写入 rollback canary,最终两端 manifest 相同。
这个演示只覆盖“可枚举的一条目标写”,不能推导出任意生产写流都可这样回退。
sequence 是典型的切换缝隙
逻辑复制不复制 sequence 当前值。正式实验切流前看到目标 sequence 当前值仍为 1,
而源端最大 order_id 已到 900000。流程显式执行 setval,目标 canary 才得到
900001。
sequence 处理要考虑:
is_called语义;- identity column 背后的实际 sequence;
- 多 writer 是否预留不重叠区间;
- cached values 与仍存活的旧连接;
- 目标独占值回退后会否冲突;
- gap 是否被业务错误地当成连续性失败。
标出不可逆动作
以下动作不应与普通切流混在一个“一键脚本”:
对 schema 使用 expand/contract:先部署双方都理解的扩展形态,完成迁移与观察,再在独立 变更中收缩旧字段。这样问题出现时可以前滚修复,而不是在数据迁移、应用发布、破坏性 DDL 三者同时发生时赌一个总回退按钮。
每次决策至少记录:
回退能力是一项需要实验证明的属性。未演练、未定义数据合并边界的“随时可回退”,只是 一句安慰。
上一节:批量装载与数据校验 · 返回本章目录 · 下一节:异构同步的语义损失 · 查看全书目录 · 查看索引中心