跳到主要内容
AURA 论文精讲

第8章:Router-Centric 实现与 Lever B 负结果

按论文 §5 + v25 工程实测重读:Phase R-big 8 阶段重构地图、Manifest v1 vs v2 wire format、9216 cohort 全表展开的算账、v25 access-graph cohort planner 集成(A→E phased rollout)、Lever B 反直觉负结果与 feedback loop 诊断、record_key reshape 的方法论教训

AURA Router-Centric Phase R-big Manifest v2 Cohort Planner Lever B Record Key Feedback Loop Negative Result Lever Ordering 论文精讲

第 7 章把协议与一致性证明讲完,本章进入论文 §5 实现章——也就是把前 7 章的算法落到 CREST 代码上的工程现实。这一章是整本教程最”反直觉负结果”密集的一章:v25 access-graph cohort planner 端到端跑通了,但 Lever B(client cohort-aware dispatch)实测半步走到位,第二步反而 collapse。理解这个负结果比理解正向收益更重要——它揭示了多 lever 系统的”激活顺序”决定结果。读完你能讲清 (1) Phase R-big 8 阶段重构怎么走的;(2) Manifest v1/v2 wire format 怎么混跑;(3) v25 cohort planner 实测数据;(4) Lever B 为什么 collapse + record_key reshape 为什么是真正的根因;(5) “Lever ordering matters” 的可推广教训。

📑 目录


1. Phase R-big 8 阶段重构地图

AURA 在 CREST 上的实现走了一条很长的工程路径。前 v23 都是 per-CN 自治控制平面(W7.4-B → W8.5 → W8.7 → W8.11 → W8.12),每个 W 都是一个增量改造。Phase R-big 是 2026-04 启动的 router-centric 重构——把控制平面从 per-CN 搬到 BenchClient 进程。

1.1 8 阶段 R0 → R7 列表

Phase主题关键改造
R0baseline 归档把 per-CN W8.11 baseline 在新分支 aura-router-centric 上跑通
R1Router 进程引入BenchClient 增加 RouterControlLoop 100ms tick + memcached manifest publish
R2CN 端拉 manifest新增 OwnerMapSubscriber 拉 memcached 解析 manifest
R3CN → router 反向上报新增 ACCESS_SUMMARY 心跳,kRouterCnId 哨兵
R49216 cohort 全表展开manifest 编码 9 表 × 1024 bucket 的全表 OwnerMap
R5InstallCohort 幂等性修 OwnerLockTable::InstallCohort 适用范围漂的陷阱
R6EvictCohort + reinstall让 epoch 更新真正生效(LOCAL 命中 17% 真因修复)
R7端到端 baselinerouter-centric atomic_per_txn 13.51, KOPS 134 baseline

🌟 关键阶段

  • R0 + R1 + R2:建立 router-centric 框架(不变化行为)
  • R3 + R4:把 ACCESS_SUMMARY 和 9216 manifest 跑通
  • R5 + R6:暴露并修 InstallCohort 幂等性陷阱
  • R7:拿到 v14 baseline 等价的 router-centric 基线

📎 工程踩坑视角:模块十五第 14 章是这 8 阶段的详细 walkthrough(含每阶段日志、每个陷阱、每个修复 PR)。

1.2 R5/R6 InstallCohort 幂等性陷阱的方法论

R7 baseline 跑出来发现:LOCAL hit rate 17%,但 atomic_per_txn 仍是 13.51 没降。诊断的过程是 AURA 工程上最值得讲的一个:

   症状:LOCAL hit 17% > 25%(4 CN 平均期望),但 atomic 没降
   
   假设 1: LOCAL hit 数据不对 → 加更细的 stats 验证 → 17% 是真的
   假设 2: 本地 lock_cmpxchg 还是发了 atomic → strace 验证 → 没有
   假设 3: cohort 没真正 promote 到 owned → 加 epoch trace → 发现 epoch 升了但 owner 没换
   
   根因:OwnerLockTable::InstallCohort 第二次调用时
        - 代码看 cohort 已经 installed → 返回成功不做事
        - 但 cohort 的 owner 字段没有被更新
        - 新 epoch 的 token 仍发给旧 owner
   
   修复:EvictCohort + InstallCohort 两步走,强制刷新 owner 状态

🌟 方法论“代码没错,适用范围变了”——单次安装的逻辑对,重复安装时缺少幂等性检查。可观测性 SLO 应该看 atomic counter 而不是 LOG_INFO。

🧠 关键洞察在 12 模块组成的系统里,一个模块的接口契约必须 explicit 写出来——InstallCohort 的”幂等性假设”原作者认为不会重复调用,但 router-centric 模式下每 100 ms tick 都会 re-install。接口契约就是”在新 deployment 下被打破的”那一类 bug 的温床


2. Manifest v1 vs v2 wire format

Phase R-big 引入了 manifest 概念——router 把”哪个 cohort 归哪个 CN”编码成字节流,发到 memcached,所有 CN 拉取后应用。

2.1 v1 wire format(R4 引入)

   ┌────────────────────────────────────────┐
   │  Header                                 │
   │   - magic (4B): "AURA"                  │
   │   - manifest_version (2B): 1            │
   │   - epoch (8B)                          │
   │   - reserved (2B)                       │
   ├────────────────────────────────────────┤
   │  wh_to_cn[]                             │
   │   - num_warehouses (4B)                 │
   │   - cn_id per wh (1B × n_wh)            │
   └────────────────────────────────────────┘

🌟 v1 容量:n_wh × 1B —— 4 wh 时只有 4 字节负载。

2.2 v2 wire format(v25 引入)

   ┌────────────────────────────────────────┐
   │  Header (manifest_version = 2)          │
   ├────────────────────────────────────────┤
   │  wh_to_cn[] (与 v1 兼容)                │
   ├────────────────────────────────────────┤
   │  Cohort section                         │
   │   - num_cohorts (4B)                    │
   │   - for each cohort:                    │
   │     - cohort_id (8B)                    │
   │     - owner_cn (1B)                     │
   │     - num_kgs (2B)                      │
   │     - kg_list (8B × num_kgs)            │
   └────────────────────────────────────────┘

🌟 v2 容量:典型 ~150 KB(1024 cohort × 平均 16 kg),最大 ~900 KB(已加 assert)。

2.3 v1 / v2 混跑

🧠 关键设计:v1 reader 看 manifest_version=2 时 只读 wh_to_cn 段,跳过 cohort 段(向后兼容)。v2 reader 看 manifest_version=1 时只用 wh_to_cn fallback path

🌟 结论新老 CN / 新老 router 可以混跑——v1 路径仍能 wh-only 路由,v2 路径加 cohort overlay。这是渐进部署的关键。

2.4 kEdgesPresentBit:CN→router heartbeat 协议演进

CN→router 的 ACCESS_SUMMARY 心跳也演化出 v2:

   v1 (AccessSummary):
       cn_id + epoch + summary_entries (key_group, count)
   
   v2 (AccessSummary + EdgeSummary):
       v1 内容 + kEdgesPresentBit + edges_ww + edges_wr (typed edges)

🌟 设计原则CN↔CN 之间保 v1(W16 owner request 不需要 typed edge),CN→router 走 v2(需要 typed edge 构图)。这就是第 4 章 §5 “ground truth 在 CN,router 来聚合” 的协议层落地。

📎 工程踩坑视角:v25 计划第 2 节列出了完整的 wire format 变更清单(OwnerRpcMessage.h 加 EdgeSummaryEntry + kEdgesPresentBit)。


3. 9216 cohort 全表展开

3.1 为什么是 9216

   9 张 TPCC 表 × 1024 bucket = 9216 个 key_group
   每个 key_group 是一个潜在 cohort(v1 一对一)
   v25 router 用 access-graph cohort planner 把它们 merge 成更大的 cohort

🌟 关键数字

bucketkey_group
WAREHOUSE10241024
DISTRICT10241024
CUSTOMER10241024
HISTORY10241024
STOCK10241024
ORDER10241024
ORDER_LINE10241024
NEW_ORDER10241024
ITEM10241024
Total92169216

3.2 全表展开的代价

🌟 存储

   每 cohort entry ≈ 16B(id 8B + owner_cn 1B + size 2B + 5B pad)
   9216 × 16B = 147 KB(v1 全展开)

可接受——manifest 大小限 900KB,9216 cohort 占 17%。

3.3 InstallCohort 9216 次的 CPU 代价

   单次 InstallCohort ~5 μs(含 hash table insert + epoch 检查)
   9216 次 = ~46 ms

🌟 结论首次启动安装 9216 cohort 耗时 ~50 ms,可接受(启动一次性)。增量 publish 只动变化的 cohort(典型 < 100 个)。

3.4 路由查找延迟

   每事务查 OwnerOfKeyGroup(kg):
   - 哈希查找 absl::flat_hash_map<kg_id, cn_id>
   - ~50 ns per lookup
   单 TPCC NewOrder 访问 ~20 keys → 1 μs total

可接受——路由层 < 1 μs 与 RDMA READ ~2 μs 相比可忽略。


4. v25 cohort planner 集成:A → E 五阶段 rollout

v25 集成 W14 PlanCohorts + W16 OwnershipPlanner 到 router 端的代码改造按 A→E 五阶段做,每阶段 gated 验证不退化。

4.1 五阶段路线

Phase范围gatingLOC
AWire format 加 v2(行为不变)smoke 等价于 v14 baseline+120 LOC
BRouter 端 AggregatedAccessGraph 扩展smoke 等价于 v14 baseline+120 LOC
CCN heartbeat v2 (typed edges)router LOG edges_ww > 0+35 LOC
DRouter cohort planner(核心)LOCAL ≥ 80% / atomic ≤ 5+150 LOC
EOwnerMapSubscriber 重构(按 cohort 聚合 UpsertCohort)同 D + 端到端跑通+80 LOC

🌟 结论每阶段都先验证 v14 baseline 不退化,再加新行为。这是 12 模块系统的典型 rollout 方法论。

4.2 关键超参确定

v25 通过 sweep 找到的关键超参:

超参选择理由
kMaxCohortSizeRouter64128/512 时 collapse 到 1 个 super-cohort,64 是甜点
kMaxPublishedCohorts1024manifest 大小 < 200KB
vertex_threshold0.00011.0 太严,PlanCohorts 经常输出 0 cohort
alpha_wr0.3第 5 章 §4 推导的 typed edge 权重
EWMA decay λ0.95半衰期 ~1.35s

4.3 sticky cohort cache:解决间歇性 emit

v25 测试发现 PlanCohorts 是间歇性 emit——4/17 ticks 输出 cohort,13/17 ticks 输出空。原因:EWMA decay 期间,某些 tick 边权刚好掉到阈值下。

🌟 修复sticky last_cohorts_ cache——空 emit 时复用上次的 cohort 集合,避免每次空 emit 都强制 cohort collapse。

📎 工程踩坑视角:模块十五第 14 章 §8.4 详细记录了这个间歇性 emit 现象 + sticky cache 修复。


5. v25 实测数据:半步走到位

v25 在 CloudLab amd103 (MN) + amd118/amd112/amd107 (CN0/1/2) 跑 1 rep × 80k txn × 4 CN W=4 TPCC。

5.1 Headline 数据

配置LOCAL hitatomic_per_txnKOPSunknown_kgabort rate
v14 baseline (per-CN, wh-only)17%13.51134< 0.1%1.5%
v25 Phase D (cohort planner ON)17%13.51133< 0.1%1.5%
v25 Phase D + E(Lever B ON)17%13.51133< 0.1%1.5%

🌟 关键观察所有指标完全持平 v14 baseline——cohort planner ON / OFF 不影响吞吐和 atomic。

5.2 这是好事还是坏事

好事面

  • ✅ Cohort planner 实测跑通端到端
  • ✅ Manifest v2 wire format 兼容性验证
  • ✅ Sticky cache、EvictCohort+InstallCohort 修复 stable
  • ✅ 没有引入回退(不破坏 baseline)

坏事面

  • ❌ 期望的 LOCAL hit 17% → 80% 没发生
  • ❌ 期望的 atomic_per_txn 13.51 → ≤ 5 没发生
  • ❌ Lever B 激活后没有任何额外提升

🌟 结论v25 是”半步走到位”——架构对了、协议跑通了,但 workload 物理特性卡住了 cohort 学习的有效性


6. Lever B 反直觉负结果与 feedback loop 诊断

6.1 Lever B 是什么

第 6 章 §5 已经介绍过:Lever B = client 端用 OwnerOfKeyGroup 按 cohort owner 路由,而不是默认的 RouteByWarehouse。

期望流程:

   1. router 学到 cohort C 主要被 CN_x 访问
   2. router 把 C 分给 CN_x
   3. client 看到 OwnerOfKeyGroup(C) = CN_x,把访问 C 的事务路由到 CN_x
   4. CN_x 上 LOCAL hit 增加
   5. atomic 减少

6.2 实测结果:collapse

激活 Lever B 后实测:

  • LOCAL hit 没变(17%)
  • active cohort 数迅速衰减(从 ~1024 → ~20)
  • router LOG cohorts=N 在 5 个 tick 内从 1024 → 20

🌟 现象:cohort 集合 collapse 到几个超级 cohort,覆盖率掉光。

6.3 Feedback loop 诊断

   t0: router 学到 cohort C 被 CN_x 70% 访问 → 把 C 给 CN_x
       cohort 集合 = {C1, C2, ..., C1024}
       
   t1: client Lever B 路由所有访问 C 的事务到 CN_x
       结果:CN_x 上 C 的 ww/wr edge 急剧上升
       其他 CN 上 C 的 edge 下降
       
   t2: router 看到 C 在 CN_x 100% hit,把 C 牢牢钉在 CN_x
       同时其他 cohort(C2, C3...)的 edge 因 Lever B 重定向也变化
       
   t3: 几个 tick 后:edge 矩阵高度偏斜
       AccessGraphProfiler 几乎所有边权重都集中在少数 super-edge
       PlanCohorts merge 把所有相关 kg 合并到一个 super-cohort
       
   t4: 5 个 tick 后:cohort 数从 1024 → 20
       Lever B 路由的事务都到同一个 super-cohort 对应的 CN
       结果:单 CN 100% 负载,其他 CN 闲置
       
   t5: AffinityRouter Queue(i) 惩罚 → 把事务推到其他 CN
       但 cohort 已经 collapse → 大部分都是 fallback 路径
       → LOCAL hit 与不开 Lever B 一样(17%)

🌟 关键洞察Lever B + 自适应 cohort planner 形成正反馈——客户端按 owner 路由 → 数据集中 → cohort collapse → 失去优势。

6.4 数学化的反馈环

   设:x(t) = cohort C 在 CN_x 上的访问占比
   
   t+1 = f(x(t)) 其中 f 是 Lever B + Planner 的复合
   
   f 的形状:
      x < 0.5: f(x) < x(rebalance)
      x > 0.5: f(x) > x(feedback amplify)
      x = 1:   f(1) = 1(fixed point)
   
   所以从随机初始点 x(0) > 0.5 开始:x → 1(cohort 全部在 CN_x)
                   x(0) < 0.5 开始:x → fixed point in (0.5, 1)
                   
   最终:所有 cohort 都收敛到 100% 集中,cohort 集合 collapse

🧠 关键洞察这是一个 1D 离散动力系统——cohort 占比 x 的迭代由 Lever B + Planner 共同决定。如果 f 在 (0.5, 1) 是凸的,必然 collapse 到 x=1。避免 collapse 的方法是让 f 凹——但这需要改 Planner 加 anti-affinity(与 Benefit 目标冲突)。


7. 真正的根因:record_key 编码

7.1 TPCC record_key 怎么编

// 来自 benchmark/TPCC/TpccBenchmarkExecutor.cc
uint64_t MakeStockKey(int w_id, int i_id) {
    return w_id * 10000 + i_id;
}

uint64_t MakeDistrictKey(int w_id, int d_id) {
    return w_id * 10 + d_id;
}

uint64_t MakeCustomerKey(int w_id, int d_id, int c_id) {
    return w_id * 30000 + d_id * 3000 + c_id;
}

🌟 关键观察record_key 是 w_id × C + i_id(C 是大常数)。然后 key_group bucket = record_key % 1024

7.2 Hash 后 wh 分布

   Stock: w_id=1, i_id=0..99999
   record_key = 10000..109999
   bucket = record_key % 1024
   → bucket 在 1024 个 slot 上 uniform 分布
   
   wh=1 的 stock 全部 record 散布在 1024 个 bucket
   wh=2 的 stock 也是
   → wh=1 和 wh=2 在 bucket 维度高度重叠(每个 bucket 都有两个 wh 的 stock)

🌟 结论bucket = record_key % 1024 在 hash 后均匀分布,与 home_wh 无关联

7.3 为什么这毁了 cohort planner

   AURA 期望:cohort 对应"同 home_wh 的 keys 集中"
   现实:    每个 cohort 是 9 表的某个 bucket(均匀分布在所有 wh)
           cohort owner 与 home_wh 几乎独立
   
   → cohort planner 决定的 owner ≈ home_wh % N_CN(仅按 wh 哈希)
   → 与 RouteByWarehouse fallback 几乎重合
   → Lever B 没有额外信息

🌟 结论TPCC 当前 record_key 编码下,cohort owner 和 wh 静态路由是同构的。Lever B 是”重复信息”,没有 information gain。

7.4 解决方法:record_key reshape

🌟 方向:把 record_key 编码改成”wh 在高位,i_id 在低位”:

// 修改后
uint64_t MakeStockKey(int w_id, int i_id) {
    return (uint64_t(w_id) << 24) | i_id;     // wh 在高位
}

效果:

   bucket = key_group_id_t(record_key) = (w_id << 24 | i_id) % 1024
   = i_id % 1024(因为 w_id << 24 是 1024 的整数倍)
   
   → 同 wh 的 stock 都在相同 i_id 的 bucket
   → 不同 wh 的 stock 分布在不同的 (w_id, bucket) 二元

但这要求 MakeKeyGroupId 也按 wh 分桶

inline key_group_id_t MakeKeyGroupId(int table_id, int w_id, uint64_t record_key) {
    return (uint64_t(table_id) << 14) | (uint64_t(w_id) << 10) | (record_key & 0x3FF);
}

这样 cohort = (table, wh, bucket) 三元,cohort 与 wh 内禀关联

7.5 为什么 v25 没做 record_key reshape

两个原因

  1. 改 record_key 编码是入侵性变更——涉及 9 张表所有访问路径,工作量大
  2. 要测 Lever B 在不动 record_key 的前提下能不能 work —— 实测证明不能

🌟 结论v25 的负结果是”必须做 record_key reshape”的实证依据

📎 工程踩坑视角:模块十五第 14 章 §8.4 完整记录了这个诊断过程 + record_key reshape 的 lever 排序。


8. “Lever ordering matters” 的可推广教训

8.1 教训本身

🌟 可推广教训当多个 lever 形成 feedback loop 时,激活顺序决定结果

   Lever 1 (record_key reshape): 让 cohort 能按 home_wh 聚簇
   Lever 2 (Lever B = client cohort-aware dispatch): 把事务路由到 cohort owner
   
   错误顺序:先开 Lever 2 → cohort collapse(v25 实测)
   正确顺序:先做 Lever 1 → cohort 与 home_wh 内禀关联 → 再开 Lever 2 → 互相增强

8.2 为什么 lever 顺序重要

   feedback loop 的 fixed point 由"初始条件"决定
   
   Lever 1 改变初始拓扑(让 cohort 与 wh 关联)
   Lever 1 之后激活 Lever 2 → 新初始条件下 collapse 不再发生(拓扑约束)
   
   Lever 1 不做就激活 Lever 2 → 旧拓扑 + 反馈环 → collapse

8.3 类似的工业案例

系统Lever 1Lever 2错误顺序后果
TiDB Region splitpartition key 设计leader balance不动 key → leader 在某 store 集中
Spark partitionreduceByKey key 设计dynamic partition coalescingkey 倾斜 → coalescing 反而把 skew 放大
AURArecord_key reshapeLever B client dispatchrecord_key 不动 → cohort collapse

🌟 结论这是分布式系统设计的”先决条件”模式——某些 lever 必须先做,否则后续 lever 自相矛盾

8.4 工程上怎么发现 lever 顺序

🧠 方法论

  1. 画 feedback 图:把每个 lever 看成系统的一个反馈环,列出每个 lever 改了什么 + 依赖什么
  2. 找 fixed point:在每个 lever 单独激活下分析 fixed point
  3. 找 lever 间的耦合:哪些 lever 共享变量?共享路径?
  4. 画”激活顺序图”:哪些 lever 依赖哪些 lever 的前置条件
  5. 顺序激活 + 每步验证:按依赖链激活,每步看 baseline 不退化

🌟 本案例:v25 的 5 阶段 A→E rollout 实际上就是这种”顺序激活 + 每步验证”方法论——但 Lever B 与 record_key 的依赖关系直到实测才暴露。

8.5 把负结果写进 paper

正面写法:“Lever B 实测无效,feedback loop collapse,需要先做 record_key reshape” —— 这才是 negative result 的方法论价值

反面写法:“Lever B 不 work,弃用” —— 失去了对系统设计的洞察

📎 工程踩坑视角:第 9 章会展开”negative regime 设计原则”——故意设计对 AURA 不利的 workload,让 reviewer 看到 AURA 的边界。

✅ 自我检验清单

  • R0–R7 8 阶段:能讲清每阶段做了什么 + R5/R6 InstallCohort 幂等性陷阱怎么暴露的
  • Manifest v1/v2:能徒手画 wire format + 解释为什么 manifest_version 在旧 reserved 字段里
  • 9216 cohort:能算 9 表 × 1024 bucket = 9216 + manifest 容量 ~150KB
  • A–E rollout:能列 5 阶段每阶段 gating 条件
  • 关键超参:能背 kMaxCohortSizeRouter=64 / kMaxPublishedCohorts=1024 / alpha_wr=0.3
  • v25 头数据:能背 LOCAL=17% / atomic=13.51 / KOPS=133 持平 baseline
  • Feedback loop:能徒手画 t0 → t5 的 cohort collapse 时序
  • Record_key 根因:能解释 w_id × 10000 + i_id 为什么哈希均匀
  • Reshape 方案:能写 (w_id << 24 | i_id) + 新 MakeKeyGroupId
  • Lever ordering:能讲清”先 record_key reshape 再 Lever B”的依赖关系 + 给出 1 个工业类似案例

第 9 章预告

第 9 章按论文 §6 + §9(端到端复现)展开 评测方法论与端到端复现

  • Bootstrap CI 怎么算
  • Acceptance gates 怎么定(atomic / KOPS / unknown / abort)
  • Negative regimes 怎么设计(让 AURA 不好看的 workload)
  • CloudLab 端到端复现脚本
  • 3-rep 取中位数 + paper §6 M6 行更新

读完第 9 章你拿到一份”能写进 paper 的 figure” 所需的完整复现包。


📚 参考资料

论文与代码

关键源文件

  • src/transaction/aura/OwnerLockTable.cc —— InstallCohort 幂等性陷阱发生地
  • src/transaction/aura/Manifest.cc —— v1/v2 wire format serializer
  • src/transaction/aura/RoutingTable.cc —— Lever B OwnerOfKeyGroup
  • benchmark/Client/RouterControlLoop.cc —— v25 cohort planner 集成
  • benchmark/Client/AggregatedAccessGraph.cc —— typed edge 聚合 + snapshot

类似工业 lever ordering 案例

  • TiDB Region split + leader balanceTiKV docs PD scheduler 章节
  • Spark dynamic partition coalescing:Spark 文档 + Databricks blog

模块内交叉引用