第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 的方法论教训
第 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 阶段重构地图
- 2. Manifest v1 vs v2 wire format
- 3. 9216 cohort 全表展开
- 4. v25 cohort planner 集成:A → E 五阶段 rollout
- 5. v25 实测数据:半步走到位
- 6. Lever B 反直觉负结果与 feedback loop 诊断
- 7. 真正的根因:record_key 编码
- 8. “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 | 主题 | 关键改造 |
|---|---|---|
| R0 | baseline 归档 | 把 per-CN W8.11 baseline 在新分支 aura-router-centric 上跑通 |
| R1 | Router 进程引入 | BenchClient 增加 RouterControlLoop 100ms tick + memcached manifest publish |
| R2 | CN 端拉 manifest | 新增 OwnerMapSubscriber 拉 memcached 解析 manifest |
| R3 | CN → router 反向上报 | 新增 ACCESS_SUMMARY 心跳,kRouterCnId 哨兵 |
| R4 | 9216 cohort 全表展开 | manifest 编码 9 表 × 1024 bucket 的全表 OwnerMap |
| R5 | InstallCohort 幂等性 | 修 OwnerLockTable::InstallCohort 适用范围漂的陷阱 |
| R6 | EvictCohort + reinstall | 让 epoch 更新真正生效(LOCAL 命中 17% 真因修复) |
| R7 | 端到端 baseline | router-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
🌟 关键数字:
| 表 | bucket | key_group |
|---|---|---|
| WAREHOUSE | 1024 | 1024 |
| DISTRICT | 1024 | 1024 |
| CUSTOMER | 1024 | 1024 |
| HISTORY | 1024 | 1024 |
| STOCK | 1024 | 1024 |
| ORDER | 1024 | 1024 |
| ORDER_LINE | 1024 | 1024 |
| NEW_ORDER | 1024 | 1024 |
| ITEM | 1024 | 1024 |
| Total | 9216 | 9216 |
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 | 范围 | gating | LOC |
|---|---|---|---|
| A | Wire format 加 v2(行为不变) | smoke 等价于 v14 baseline | +120 LOC |
| B | Router 端 AggregatedAccessGraph 扩展 | smoke 等价于 v14 baseline | +120 LOC |
| C | CN heartbeat v2 (typed edges) | router LOG edges_ww > 0 | +35 LOC |
| D | Router cohort planner(核心) | LOCAL ≥ 80% / atomic ≤ 5 | +150 LOC |
| E | OwnerMapSubscriber 重构(按 cohort 聚合 UpsertCohort) | 同 D + 端到端跑通 | +80 LOC |
🌟 结论:每阶段都先验证 v14 baseline 不退化,再加新行为。这是 12 模块系统的典型 rollout 方法论。
4.2 关键超参确定
v25 通过 sweep 找到的关键超参:
| 超参 | 选择 | 理由 |
|---|---|---|
kMaxCohortSizeRouter | 64 | 128/512 时 collapse 到 1 个 super-cohort,64 是甜点 |
kMaxPublishedCohorts | 1024 | manifest 大小 < 200KB |
vertex_threshold | 0.0001 | 1.0 太严,PlanCohorts 经常输出 0 cohort |
alpha_wr | 0.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 hit | atomic_per_txn | KOPS | unknown_kg | abort rate |
|---|---|---|---|---|---|
| v14 baseline (per-CN, wh-only) | 17% | 13.51 | 134 | < 0.1% | 1.5% |
| v25 Phase D (cohort planner ON) | 17% | 13.51 | 133 | < 0.1% | 1.5% |
| v25 Phase D + E(Lever B ON) | 17% | 13.51 | 133 | < 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
两个原因:
- 改 record_key 编码是入侵性变更——涉及 9 张表所有访问路径,工作量大
- 要测 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 1 | Lever 2 | 错误顺序后果 |
|---|---|---|---|
| TiDB Region split | partition key 设计 | leader balance | 不动 key → leader 在某 store 集中 |
| Spark partition | reduceByKey key 设计 | dynamic partition coalescing | key 倾斜 → coalescing 反而把 skew 放大 |
| AURA | record_key reshape | Lever B client dispatch | record_key 不动 → cohort collapse |
🌟 结论:这是分布式系统设计的”先决条件”模式——某些 lever 必须先做,否则后续 lever 自相矛盾。
8.4 工程上怎么发现 lever 顺序
🧠 方法论:
- 画 feedback 图:把每个 lever 看成系统的一个反馈环,列出每个 lever 改了什么 + 依赖什么
- 找 fixed point:在每个 lever 单独激活下分析 fixed point
- 找 lever 间的耦合:哪些 lever 共享变量?共享路径?
- 画”激活顺序图”:哪些 lever 依赖哪些 lever 的前置条件
- 顺序激活 + 每步验证:按依赖链激活,每步看 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” 所需的完整复现包。
📚 参考资料
论文与代码
- AURA paper §5 实现章:paper_lock_ownership_cn/sections/5_implementation.tex
- v25 cohort planner 计划:plans/rippling-wishing-emerson.md
- CREST-aura-impl:CREST-aura-impl/CREST-Opensource-0007/
关键源文件
src/transaction/aura/OwnerLockTable.cc—— InstallCohort 幂等性陷阱发生地src/transaction/aura/Manifest.cc—— v1/v2 wire format serializersrc/transaction/aura/RoutingTable.cc—— Lever B OwnerOfKeyGroupbenchmark/Client/RouterControlLoop.cc—— v25 cohort planner 集成benchmark/Client/AggregatedAccessGraph.cc—— typed edge 聚合 + snapshot
类似工业 lever ordering 案例
- TiDB Region split + leader balance:TiKV docs PD scheduler 章节
- Spark dynamic partition coalescing:Spark 文档 + Databricks blog
模块内交叉引用
- 本模块第 5 章:访问图与 cohort 学习 —— EWMA decay / typed edge 的算法前置
- 本模块第 6 章:Owner 规划与亲和路由 —— Lever B 的代码层定义 + feedback loop 数学化前置
- 模块十五第 13 章:从 +3.76% 到 +1.06%:OwnerMap 广播的工程教训 —— 类似”负结果方法论”模板
- 模块十五第 14 章:Router-centric 重构 —— 本章 §1 + §6 + §7 的完整工程现场记录