Shadow Mode:新功能上线的零风险验证模式
翻最近 10 天的 git 记录,127 个 commit 里 “shadow” 出现了超过 20 次:
feat: SGR ... shadow-mode
feat: ARE Phase 1 ... shadow-mode
feat: DCB Detector ... shadow-mode
feat: EV Engine Phase 1 ... shadow recording
fix: SGR shadow mode bypass riskGate for signal recording
fix: HedgeLock recovery v2 — shadow-mode bypass P1
不是巧合——这不是某个工程师的个人偏好,而是一个系统化的工程模式。
什么是 Shadow Mode
新功能上线时,不直接接管生产流量,而是在旁路运行,只记录数据和决策日志,不产生实际副作用。
生产流量 ──┬──► 旧逻辑(真实执行)
│
└──► 新逻辑(Shadow:只记录,不执行)
│
▼
shadow_log 表
├─ 验证决策是否正确
├─ 验证无异常报错
└─ 验证 PnL/指标符合预期
验证通过后,切开关让 shadow 逻辑接管。
这个模式和广告系统的 AB 实验原理完全一样:新策略先在少量流量上跑,不真正出价,只记录 would_have_bid 和 would_have_won。确认 CTR 没跌再切。
这次 10 天里,哪些功能用了 Shadow
| 功能 | Shadow 记录什么 | 跑了多久 |
|---|---|---|
| SGR 网格策略 | 虚拟订单 PnL + 触发条件 | 数小时 |
| ARE 风控引擎 | 决策 ALLOW/DENY + 决策理由 | 当天 |
| DCB 死猫反弹检测 | 检测信号 + 5 状态机 | 进行中 |
| EV Engine | 期望值统计 + 状态向量 | 数小时 |
| Fracture v4 | MFE/MAE + 信号质量 | 数天 |
每个新功能,不管多小,都先在 shadow 模式下跑。
为什么这么偏执? 因为三次痛过:
- 多头保护策略(history):新策略直接上线,误杀了正常交易,当天亏损。如果先跑 shadow,一眼就能看出
DENY率异常。 - PnL 单位错误(本次):SGR 的 recordPnl 把百分比当 USD 写。如果直接实盘,风险敞口计算全错。Shadow 日志里一看就知道 PnL 数字不对。
- 数据库字段名不匹配(本次):ev_shadow_log 列名不符合 ORM 约定,实盘会静默写不进去。Shadow 模式下发现日志为空,立刻排查。
Shadow 不是多此一举,是每次事故前少做的那一步。
Shadow 的三种形态
1. 纯信号记录(SGR、DCB)
// Shadow: 只记录信号,不下单
shadowLog.record(signal);
// 真实: 在风险检查后下单
riskGate.check(order) → placeOrder(order);
最简单。信号质量是核心指标——信号频率、信号方向的胜率、触发后价格走势。跑几天数据就够判断策略是否值得上线。
2. 决策对比(ARE)
// 旧风控: 各策略自己的检查
oldResult = strategy.check(order);
// 新引擎 Shadow: 旁路计算
newResult = areRiskGate.check(order);
// 对比日志
shadowLog.record(oldResult, newResult, diff);
ARE 的 shadow 不只是记录自己的决策,还和旧逻辑对比。新旧不一致 → 要么旧逻辑有遗漏,要么新逻辑有误杀。 两边都要看。
3. 指标验证(EV Engine、Fracture)
// Shadow: 旁路统计
evEngine.collect(stateVector, outcome);
// 不产生交易决策,但产出的 EV 统计被后续策略引用
这种最微妙——shadow 阶段的数据质量必须高,因为别的模块会消费这些统计结果。Shadow 数据的消费者不关心你是不是 shadow,它只关心数据对不对。
Shadow 的风险:阴影也会犯错
Shadow 模式虽然不产生实际交易,但它也有风险:
- Shadow 本身崩溃:如果 shadow 逻辑有 NPE,会不会拖垮主流程?不会——所有 shadow 调用都包在 try-catch 里,异常只记日志,不影响主流程。
- Shadow 数据污染下游:EV Engine 的统计在 shadow 阶段如果被别的策略消费,错误数据会导致错误决策。解法是:shadow 阶段的数据打标记
source=SHADOW,消费者可以选择忽略。 - Shadow 忘记关闭:Fracture v4 跑了 shadow 好几天没切,因为没有人明确负责”验证通过后切换”这个动作。这个后来改了流程——每个 shadow 功能上线时,assign 一个 owner 和 due date。
元结构映射:广告系统的三阶段发布
| 阶段 | 广告系统 | 交易系统 Shadow |
|---|---|---|
| 离线验证 | 历史日志回放 | 单元测试 |
| 旁路验证 | 1% 流量记录 would_have_bid | Shadow mode |
| 小流量 | 5% 流量真实出价 | 单标的实盘 |
| 全量 | 100% | 全量标的 |
本质相同:任何新逻辑在接管生产流量前,必须在旁路证明自己。 跳过这一步就是在赌。
教训
- 每个新功能默认 shadow first。 不是”重要的跑 shadow,不重要的直接上”,而是所有新逻辑不例外。因为”不重要”的判断往往是错的——那次 PnL 单位错误就出在一个”不重要”的改动里。
- Shadow 不是”记录一下就完了”。 必须有明确的验证标准和切换时间。跑了 shadow 三天没人看 = 没用。
- Shadow 数据要打标记。 下游消费者要知道这个数据来自 shadow,可以选择信任或不信任。
一句话:Shadow mode = 新功能的安全网。跳过 shadow 直接上线,等于不开降落伞跳伞——可能没事,但有事就是大事。