跨团队高风险迁移、灰度切换与回滚边界¶
30 秒版(开场)¶
高风险迁移先定义 source of truth、不变量和回滚边界,再谈双写或切流。我通常采用 expand → 可重放复制/回填 → shadow compare → 按 cohort 切读 → 切写/冻结旧入口 → contract/decommission。双写如果没有同一事务或 outbox/CDC,只会制造两个真相;应用版本回滚也不能逆转已写入的新 schema、事件或链上副作用。Staff 面要讲清技术门禁、跨团队 owner、风险登记、停止条件和可量化结果,而不是“大家配合完成迁移”。
3 分钟版(精讲深度)¶
迁移前先写五项:
- Authority:哪个系统在每个阶段是写入与读取事实源。
- Invariants:例如账本按币种守恒、同一 observation 只映射一个 canonical version、 同一业务幂等键最多产生一次不可逆副作用。
- Compatibility:N/N-1 代码、schema、event 和 API 的读写矩阵。
- Gates:完整性、正确性、新鲜度、性能、可运维性和安全门禁。
- Rollback:能回退流量、代码、schema 还是数据;超过哪个点只能 forward-fix。
10 分钟版(迁移状态机)¶
flowchart LR
A["discover + invariants"] --> B["expand compatibility"]
B --> C["replicate / backfill"]
C --> D["shadow read + reconcile"]
D --> E["canary cohorts"]
E --> F["cut read authority"]
F --> G["cut write authority"]
G --> H["stabilize + rollback window"]
H --> I["contract + decommission"]
每一阶段有显式入口/出口条件,失败就停在当前阶段,不靠“先全部切完再观察”。
1. Discover:不要迁移未知系统¶
- 列出所有 writer、reader、批处理、报表、应急脚本、数据修复和隐含 session/缓存状态。
- 画数据 lineage:输入、派生、复制、消费者、保留和删除要求。
- 用流量与查询日志验证依赖,不只访谈 owner。
- 为每条不变量指定验证查询、owner 和容忍阈值。
- 确认监管、密钥、PII、数据驻留及审计要求。
迁移计划中最危险的通常不是“主服务”,而是无人维护的 cron、人工 SQL 和只在月末运行的流程。
2. Expand:先兼容,再切流¶
- 新增 nullable/default 字段或新 event version,旧代码仍能读写。
- 消费者先支持新旧格式,生产者后切新格式。
- API 用 additive change;删除/改义另开 contract 阶段。
- 数据库索引用并发构建/受控窗口,并验证 valid/usable。
- 升级序列覆盖滚动期间 N 与 N-1 同时运行。
若旧版本无法理解新写入,就不存在真正的代码回滚;这必须在发布前暴露。
3. Replicate/Backfill:只保留一个写入 authority¶
优先模式:
source transaction
-> domain rows + outbox/扫块水位
-> idempotent relay/CDC
-> target upsert/append by stable source identity
-> checkpoint + reconciliation
不受控的应用双写有两个独立网络结果:A 成功/B 失败、A timeout/B 成功等,会产生两个事实源。 若确需双写,应明确哪个成功决定业务 ACK、失败如何持久化补偿,以及目标如何按 source identity 幂等;“两个都写,失败就重试”不是协议。
Backfill 要:
- 固定 snapshot/watermark,记录范围与 decoder/schema version;
- 小批、可暂停、可重试,checkpoint 单调推进;
- 与 realtime 在稳定 identity/连续 hash 上交接;
- 限速保护线上流量,并测剩余时间;
- 对历史坏数据有 quarantine,而不是为过门禁静默丢弃。
4. Shadow compare:影子不是第二个副作用入口¶
Shadow read 将同一请求送给新旧读路径,但只让当前 authority 响应用户。比较应先规范化:
- 排序、时间精度、空值、分页和最终性水位;
- 金额/计数守恒、集合差异和字段级 diff;
- 允许的 eventual lag 窗口与超时;
- mismatch 分类:真实数据错、decoder 版本差、查询语义差或观测噪声。
对支付、签名、下单和广播不能简单 shadow 执行真实写副作用。可使用 dry-run、模拟、录制输入或 在隔离账户执行。
5. Canary cohort:按故障域切,不按随机百分比迷信¶
合理 cohort 可以是:
- 只读内部流量 → 低风险租户 → 小链/小市场 → 普通租户 → 高价值业务;
- 单 shard/region → 多 shard → 全量;
- finalized 历史查询 → safe/head 近实时查询。
每批有最小观察窗口、自动/人工停止条件和回退开关。随机 1% 对共享数据库 migration、全局 CRD 或单一撮合状态机可能没有隔离意义。
6. Cutover 与 rollback¶
| 层 | 常见可回退项 | 不一定可回退项 |
|---|---|---|
| 流量 | route/feature flag/read authority | 已对外发出的 Webhook/交易 |
| 应用 | 兼容窗口内回退镜像 | 旧代码看不懂的新数据 |
| 数据库 | 保留旧列/双读 | destructive DDL、错误 backfill |
| 事件 | consumer 切回旧 topic/version | 已被外部消费者处理的事件 |
| 链上 | 停止后续广播 | 已最终化交易/合约调用 |
| IaC/CRD | Git desired state/部分资源 | provider 外部副作用、CRD 数据与存储迁移 |
Rollback plan 必须写明“回退后由谁继续同步数据”。只切回旧读路径,却停止 target→source 兼容同步,会在第二次切换时重新出现数据洞。
可复用案例:链数据从单体 PostgreSQL 迁到 raw lake + ClickHouse¶
以下仍是模板,必须替换成真实项目证据。
Situation¶
单体 PostgreSQL 同时承担 canonical 扫块水位、raw event、API 点查和全表分析。随着
[链数/ 日增量]增长,backfill 与在线查询互相影响;reorg 修复按高度覆盖,无法保留 fork 证据。 涉及 indexer、数据平台、钱包、风控和 BI[N]个团队。
Task¶
我负责在不中断
[关键 SLO]的前提下,把 raw evidence、canonical control plane 和分析 serving 分层;迁移[N]个消费者,并保留[时间]的可回退窗口。
Action¶
- 定义 observation identity、canonical version、finalized watermark 和金额/计数守恒门禁。
- 比较“垂直扩 PostgreSQL”“直接全量换 ClickHouse”“raw lake + 小型 control plane + ClickHouse serving”,并记录成本与不可逆点。
- 先让旧系统写 outbox/CDC,以 block hash 身份幂等落 raw lake 与 ClickHouse;禁止 target 反向成为第二个写 authority。
- 按高度区间 backfill,与 realtime 用连续 parent hash overlap 交接。
- 对查询做 shadow compare;分别统计 missing、extra、wrong-canonical、decoder mismatch。
- 先切 finalized 历史读,再切近实时 API;canonical commit 仍由小型事务控制面负责。
- 达到
[完整性/延迟/成本]门禁后冻结旧写入口,保留旧表只读,最后按消费者归零证据下线。
Result¶
backfill 完成时间: [真实值]
canonical mismatch: [切流前/后真实值]
API P95/P99: [真实值]
在线 PostgreSQL CPU/I/O: [真实值]
单位查询扫描/存储成本: [真实值]
迁移完成率与遗留范围: [真实值]
如果没有完整成功案例,也可以诚实讲“迁到哪一阶段、为何暂停、阻止了什么风险”。Staff 信号来自 高质量判断和风险控制,不是必须把项目包装成全赢。
跨团队运行机制¶
| 机制 | 目的 |
|---|---|
| 单一 migration DRI + 每系统 owner | 避免“所有人负责”等于没人负责 |
| 决策日志与风险登记 | 记录 owner、概率、影响、触发器、缓解与截止日期 |
| 每阶段 go/no-go review | 用门禁而非进度压力决定是否推进 |
| 统一 dashboard | 各团队看到同一水位、diff、lag 和 SLO |
| office hour / migration kit | 降低消费者 adoption 成本 |
| break-glass 与停止权 | 当 guardrail 触发时任何值班方可暂停 |
| deprecation deadline | 给旧路径 owner、迁移支持和升级机制 |
深挖问答¶
- 双写为什么危险?
两个系统没有共同提交边界,会出现部分成功和 unknown;必须有单一 authority、outbox/CDC、 幂等和补偿协议。 - Shadow 一致率 99.99% 能否切?
先看剩余 0.01% 的类型和业务影响;一笔资金错误可能比百万条展示差异更重要。 - 如何选择 canary cohort?
选能隔离故障、代表真实工作负载且可回退的边界,不盲目随机百分比。 - 代码能回滚为何数据不能?
新版本可能已写入旧代码不理解的 schema/event,或触发不可逆外部副作用。 - 何时删除旧系统?
消费者归零、回滚窗口结束、数据保留/审计完成、恢复演练通过且 owner 签字后。 - 项目延期怎么向管理层汇报?
以风险、未通过门禁、选项和新预测说明;不隐藏坏消息,也不只报“完成百分比”。
反模式与错误表达¶
- “新旧系统双写一段时间,最终自然一致。”
- “Shadow 结果差不多就可以切。”
- “先全量迁完,再补监控和回滚。”
- “应用版本能 rollback,所以数据库也能。”
- “随机切 1% 流量适合所有系统。”
- “迁移完成”但旧 writer、cron 和人工脚本仍在运行。
- 用示例里的链数、性能或成本数字冒充自己的项目结果。