跳到主要内容
分离式事务的动态锁所有权

第12章:W15 / W16 / W18 —— 跨 CN 一致性的两条独立路径

W14 让 cohort 稳定后,跨 CN 还差两件事:把事务路由到正确的 home_cn(W15 TransactionRouter + ProxyTxnQueue),以及让所有 CN 对『谁拥有哪个 cohort』达成共识(W16 access summary heartbeat + W18 plan consensus)。本章拆解两条路径的工程细节、为什么走 TCP 不走 RDMA、以及『全局一致反而比独立宇宙吞吐还差』的反直觉实测

AURA Transaction Router Access Summary Plan Consensus W15 W16 W18 OwnerRpc 反直觉收益

第 11 章把 cohort 稳定性立起来了:W14 + Jaccard 继承 + 倒排索引让”cohort 是个稳定的语义对象”这件事在工程上可执行。但稳定 cohort 本身不会自己干活——还要回答两个问题:第一,事务怎么知道自己该跑在哪个 CN 上(应用层 argmax + dispatch,对应论文 §3.5 affinity router);第二,所有 CN 怎么对『谁拥有哪个 cohort』达成共识(对应论文 §3.4 ownership planner 的全局一致性)。这两条路径在 AURA 实现里是独立的两条工程线,分别是 W15(事务级路由)和 W16/W18(access summary heartbeat + plan consensus)。本章把它们拆开讲,重点讲三处工程决策:为什么 W15 走 TCP 不走 RDMA、W16 heartbeat 为什么要在 argmax 之前跑、以及最后一个会让人深夜质疑人生的实测发现——W18 开了全局共识反而比 W7.4-B “独立宇宙” 吞吐还差

📑 目录


1. W14 之后还剩什么 —— 两条独立的跨 CN 路径

W14 输出的稳定 cohort 是个带 recommended_owner 标签的语义对象。但下游”实际把事务执行送到 owner 那里”和”所有 CN 对 owner 标签达成共识”是两件不同的事情:

        W14 输出:
        ┌──────────────────────────────────────────────┐
        │ CohortPlan { id, members, recommended_owner }│
        └──────────────────────────────────────────────┘
                   │                       │
        ┌──────────▼──────────┐ ┌──────────▼──────────────┐
        │ W15: 事务级路由     │ │ W16/W18: 跨 CN owner 共识│
        │ (per-txn dispatch)  │ │ (cross-CN consensus)     │
        └─────────────────────┘ └─────────────────────────┘
        把"这笔事务该跑在哪"做对    把"全 CN 对 owner 看法一致"做对
        消费方:bench worker        消费方:W14 PlanCohorts 的
                                    argmax / TransferController

两条路径是正交的

  • W15 关注单笔事务的执行地点 —— 用 OwnerRpc::ExecuteTxn 把整个 txn 派到 home_cn 去跑;
  • W16/W18 关注全 CN 对 owner 的共识 —— 每个 CN 周期性广播自己看到的 hot KG access count,本地 argmax 时用全 CN 矩阵而不是只看自己的 kg_count_。

🍎 直觉比喻:W15 像 Uber 调度——根据司机分布把订单分到最近司机那里;W16/W18 像 Uber 司机之间的”我现在在哪、空不空”的实时位置广播——没有这层广播,调度算法只能猜,每个调度员都只看自己附近的司机,分单就会偏。

🌟 结论W15 和 W16/W18 不是同一条路径的两阶段,它们是两个并行问题的解。开任一个都比都关有信息增益;都开才能完整覆盖 §3.5 affinity router 的设计


2. W15 事务级路由 —— TransactionRouter::Resolve 的 argmax

W15 的入口是 TransactionRouter::Resolve —— 一个静态、无副作用、纯计算的函数:

// TransactionRouter.cc:15-70(节选 + 注释)
TransactionRouter::Decision TransactionRouter::Resolve(
    const std::vector<key_group_id_t>& write_kgs,
    std::size_t num_cns,
    cn_id_t     self_cn_id) noexcept {
    Decision d{};
    d.home_cn = self_cn_id;
    d.reason  = Reason::kNoData;

    if (write_kgs.empty()) {
        // 只读事务没人需要派;本地跑。
        return d;
    }

    // 每个 write_kgs 里的 kg 投一票给它当前的 argmax owner。
    std::uint32_t votes[kMaxCnIdsLocal] = {0};
    for (auto kg : write_kgs) {
        const cn_id_t owner = AuraControlLoop::ArgmaxOwnerByAccess(
            kg, num_cns, self_cn_id);
        if (owner < num_cns) ++votes[owner];
    }

    // home_cn = argmax votes。self tie-break:投票打平时优先本地。
    cn_id_t best = self_cn_id;
    std::uint32_t best_v = votes[self_cn_id];
    for (std::size_t i = 0; i < num_cns; ++i) {
        if (votes[i] > best_v) {
            best = static_cast<cn_id_t>(i);
            best_v = votes[i];
        }
    }

    d.home_cn = best;
    if      (best_v == 0)            d.reason = Reason::kNoData;
    else if (best == self_cn_id)     d.reason = Reason::kSelfCover;
    else                              d.reason = Reason::kRemoteBest;
    return d;
}

三个返回原因 Reason

枚举值含义行为
kSelfCoverargmax = 本地 CN本地直接执行,省事
kRemoteBestargmax = 某个 peer CNOwnerRpc::ExecuteTxn 派过去
kNoData没有 write set 或者 access summary 矩阵全 0兜底本地执行

Reason 枚举的命名陷阱:第一版用 SELF_COVER / REMOTE_BEST / NO_DATA,编译失败 —— NO_DATA<netdb.h> 里的宏。所有要”看起来像常量”的标识符放进 C++ 头时永远用 k 前缀,就是这个故事的产物。

每个调用点(实战中只在 BenchCoroutine 的 TPC-C NewOrder 分支)拿到 Decision 后的逻辑:

// TpccBenchmarkExecutor.cc 节选
if (AuraRuntime::RouteTxnEnabled() &&
    AuraRuntime::AccessSummaryEnabled() &&
    num_cns > 1) {
    std::vector<key_group_id_t> write_kgs = {
        MakeKeyGroupId(WAREHOUSE_TABLE, w_id),
        MakeKeyGroupId(DISTRICT_TABLE,  district_kg),
        // ...每个 STOCK_TABLE 也加进去
    };
    auto d = TransactionRouter::Resolve(write_kgs, num_cns, self_cn_id);
    if (d.reason == Reason::kRemoteBest) {
        // 序列化 param,OwnerRpc::ExecuteTxn 派到 d.home_cn
        OwnerRpc::ExecuteTxn(d.home_cn, self_cn_id, ...);
        return;
    }
    // 否则本地跑
}
TxnNewOrder(...);

🌟 关键设计Resolve 是 stateless 的;TPC-C executor 调用一次就拿到了完整 dispatch 决策。Resolve 内部读 ArgmaxOwnerByAccess —— 这里才是 W16/W18 让 argmax 全局一致的注入点。


3. ProxyTxnQueue —— OwnerRpcServer 与 worker coroutine 的握手

W15 的派事务路径走 OwnerRpc::ExecuteTxn —— TCP 发出去后,对端 OwnerRpcServer网络线程里收 EXECUTE_TXN_REQ。但 CREST 的事务执行栈跑在协程里(boost::coroutine + per-thread CoroutineScheduler),网络线程不能直接调 TxnNewOrder

中间需要一个队列 + 等待握手结构。ProxyTxnQueue 就是这个东西:

// ProxyTxnQueue.h(节选)
struct ProxyTxnTask {
    cn_id_t       caller_cn;
    std::uint64_t txn_id;
    std::uint32_t workload;
    std::uint32_t txn_type;
    const std::uint8_t* param_bytes;
    std::uint32_t param_len;

    // 握手 ——
    std::atomic<bool>        done{false};
    std::mutex               cv_mtx;
    std::condition_variable  cv;

    // 结果回填 ——
    bool                     committed;
    std::int32_t             abort_reason;
    std::uint64_t            exec_latency_ns;
    std::uint64_t            validate_latency_ns;
    std::uint64_t            commit_latency_ns;
};

数据通路:

[server side: 网络线程]
OwnerRpcServer.HandleExecuteTxnReq:
    1. validate magic + read param_bytes
    2. ProxyTxnTask task{...};                    // 栈上分配
    3. ProxyTxnQueue::Push(&task);
    4. {
         std::unique_lock<std::mutex> lk(task.cv_mtx);
         task.cv.wait_for(lk, 5s, []{return task.done.load();});
       }
    5. 用 task.{committed, latency_ns...} 拼 EXECUTE_TXN_REPLY 发回

[server side: worker coroutine(每 iteration)]
BenchCoroutine 主循环 prologue:
    ProxyDrain(yield, coro_id, t_ctx, txn):
        ProxyTxnTask* t = ProxyTxnQueue::TryPop();
        if (!t) return false;
        // 反序列化 t->param_bytes 成 NewOrderTxnParam
        NewOrderTxnParam param;
        param.Deserialize(t->param_bytes, t->param_len);
        // 跑事务
        TxnNewOrder(txn, t_ctx, param);
        // 回填结果
        t->committed      = txn.committed();
        t->exec_latency_ns = ...;
        // 通知等待方
        {
            std::lock_guard<std::mutex> lk(t->cv_mtx);
            t->done.store(true);
        }
        t->cv.notify_one();

为什么 task 是栈分配 + cv 同步、不是 heap + 异步回调

方案优点缺点
当前:栈 + cv(同步阻塞)5s timeout 限定生命周期;网络线程 wait 不消耗 CPU网络线程被阻塞,N 个并发派来的 txn 需要 N 条网络线程;规模上限取决于 N
备选:heap + 回调网络线程不阻塞,吞吐高task 生命周期复杂;需要单独的 GC 路径;4-CN W=4 用不到

🌟 当前规模下栈+cv 已经够:4-CN W=4 时,~75% 的 NewOrder 派到 peer,但每个 peer 同时只可能有一两个 in-flight ExecuteTxnReq,1-2 条网络线程就能扛住。规模上去再换异步。

关键 deserialization bug:第一版 NewOrderTxnParam::Deserializereturn src; 桩函数,导致整个派过来的事务参数读取全是垃圾值。修复后是按 Serialize 反向逐字节镜像:先 N items header,再 N 个 stock entry,再 W/D/customer 字段。任何 RPC 路径必须先单测 round-trip:Serialize → bytes → Deserialize → 比较原始结构,这是写完一遍才会有的肌肉记忆。


4. 为什么走 TCP 不走 RDMA —— kSlotBytes=256 装不下 NewOrder ~450B 参数

CREST 已经有一条 RDMA SEND/RECV 双向通道(OwnerRpcRdma),W6.1 后还专门做了 IBV_SEND_INLINE 优化让小消息延迟降到 ~5µs(vs TCP loopback ~15µs)。W15 派事务为什么不直接复用?

答案在 OwnerRpcRdma 的 slot 大小:

// OwnerRpcRdma.h:158
static constexpr std::size_t kSlotBytes = 256;  // header(40) + payload

每个 RDMA slot 256 B。Header 占 40 B,payload 实际 216 B。

NewOrder 的 NewOrderTxnParam 序列化后的大小:

struct NewOrderTxnParam {
    uint32_t w_id;                      // 4
    uint32_t d_id;                      // 4
    uint32_t c_id;                      // 4
    uint32_t o_carrier_id;              // 4
    uint64_t o_entry_d;                 // 8
    bool     all_local;                 // 1
    uint8_t  ol_cnt;                    // 1
    // 后面跟 ol_cnt 个 NewOrderItem,每个 16 B:
    //   uint32_t ol_i_id;
    //   uint32_t ol_supply_w_id;
    //   uint32_t ol_quantity;
    //   uint32_t pad;
};
// ol_cnt 通常 5-15(TPC-C 标准 mean=10)

实际样本:典型 ol_cnt=10 → header 26 B + 10×16 B items = 186 B,加上 padding/length prefix 约 200-220 B;ol_cnt=15 → 26 + 240 = 266 B;worst case 接近 450 B(当 ol_cnt 走到 TPC-C 规定的 max=15 且有多 warehouse 远程 item)。

序列化 size走 RDMA slot=256 B 是否可行
200 B(typical)✅ 能装下
266 B(ol_cnt=15)❌ 超
450 B(worst case)❌ 远超

🧠 关键设计决策最差情况要兜住。如果 80% 的事务走 RDMA、20% 偶尔超出 slot 要回退 TCP,路径切换的复杂度(RDMA 失败 → 重试 TCP / 路径选择逻辑 / 不同 latency 模型)抵消了 RDMA 快 10µs 的收益。统一走 TCP反而更干净。

📑 替代方案的工程比较

方案优点缺点
当前:统一 TCP(W15)一条路径,没有 fallback 逻辑;ol_cnt 上限 32 都能扛单笔派事务 ~15µs 而非 ~5µs
把 RDMA slot 扩到 1KB单次延迟 ~6µs(RDMA 大 SEND)64-slot ring 占 64KB MR,规模上去内存占用涨 4×;slot scarcity 时 backpressure 处理变复杂
多 SEND 分包slot 不变,能装 worst case协议层 reassembly;CAS / sequence number 引入;序列化反复打包
RDMA WRITE 到对端 buffer + doorbell单次 ~3µs;payload 任意大小需要双方先交换 MR rkey;server 侧需要 polling buffer 头部,CPU 不空闲

🌟 结论RDMA 优势在小消息低延迟;事务参数本身不”小”,强行塞进 slot 的代价比 TCP 慢一点的代价大。W15 走 TCP 是个理性选择,不是退而求其次

未来如果改 LOTUS 风格”只派一个 KG ID + procedure name”那种小 RPC,再回头用 RDMA 不迟。


5. W16 access summary heartbeat —— 解决 bootstrap bias

TransactionRouter::ResolveArgmaxOwnerByAccess(kg, num_cns, self) 拿 owner。这个函数怎么实现的?最朴素的版本:

cn_id_t ArgmaxOwnerByAccess(kg, num_cns, self) {
    return self;  // "everything is mine"
}

每个 CN 独立看,全部把自己当作每个 kg 的 owner —— W7.4-B 独立宇宙的行为。但这违反 §3.4 不变式 I1。

W16 的修复是引入”跨 CN 访问矩阵”——每个 CN 周期性广播”我看到 KG=k 的访问计数 = c”,所有 CN 把这些计数累在一张矩阵里:

// AuraControlLoop.cc:55-60
struct CrossCnRow {
    std::array<double, kMaxCnIds> per_cn;  // per_cn[i] = CN i 报告的访问计数
};
absl::flat_hash_map<key_group_id_t, CrossCnRow> cross_cn_kg_counts_;

ArgmaxOwnerByAccess 用这张矩阵:

// AuraControlLoop.cc:250-280(语义版本)
cn_id_t ArgmaxOwnerByAccess(kg, num_cns, self) {
    auto it = cross_cn_kg_counts_.find(kg);
    if (it == cross_cn_kg_counts_.end()) return self;  // no data tie-break
    const auto& row = it->second;
    // 关键:self 行用本地 kg_count_ 覆盖(见下文 bootstrap bias)
    double self_local = kg_count_.count(kg) ? kg_count_[kg] : 0.0;
    cn_id_t best = self;
    double best_v = self_local;
    for (cn_id_t i = 0; i < num_cns; ++i) {
        if (i == self) continue;  // self 已 overlay
        if (row.per_cn[i] > best_v) {
            best = i; best_v = row.per_cn[i];
        }
    }
    return best;
}

Bootstrap bias 问题

第一个开始广播的 CN 在矩阵里只有自己的一行有数据,其它 CN 都是 0。这个 CN 的 argmax 会把所有 KG 都判给自己,因为它的 per_cn[self] > 0 而 per_cn[others] = 0。如果不修复,第一个广播的 CN 就单方面”宣称拥有所有 KG”。

修复(W16):ArgmaxOwnerByAccess 在算 argmax 之前用本地 kg_count_ 覆盖 self 行

// AuraControlLoop.cc:265-275(注释完整版本)
// "Same row that IngestAccessSummary(self, ...) would deposit
//  next heartbeat. This breaks bootstrap bias: before the first
//  heartbeat round, self_local from kg_count_ is already the
//  ground truth — overlay it here so argmax doesn't tilt to
//  whichever CN broadcasts first."

每个 CN 自己的访问计数本来就是已知的(kg_count_ 在每 tick 自然更新)——为什么要等自己的 heartbeat 才能用?直接 overlay 就行。这条简单的修复消掉了第一轮共识阶段的”谁开口谁拥有所有”陷阱。

Heartbeat 调用节奏:

// AuraControlLoop.cc:507-509
if (AuraRuntime::AccessSummaryEnabled() &&
    (cur_tick % ACCESS_SUMMARY_HEARTBEAT_TICKS == 1)) {
    // 每 ACCESS_SUMMARY_HEARTBEAT_TICKS 个 tick 一次,约 100ms
    // 取本地 top-K 热 KG(默认 256)广播给所有 peer
}

每 100ms 一次而不是每 tick 一次:每 5ms 广播 4 CN × 256 entries × 16 B/entry = 16 KB ×4 ×200 tick/s = 12 MB/s 控制带宽——浪费。100ms 一次降到 0.6 MB/s 完全可接受。

🌟 结论W16 把”跨 CN 谁看到了什么”从 W7.4-B 的隐式独立判断升级成显式矩阵共识,并且在 self overlay 这条小细节上避开了 bootstrap bias 陷阱


6. W18 plan consensus —— 让 argmax 全局一致

W16 让 ArgmaxOwnerByAccess 是全 CN 一致的。但 W14 的 PlanCohorts 自身有更深一层:vertex_weight(顶点权重,决定哪些 KG 通过 PROMOTE_THRESHOLD 进 cohort 的种子集合)默认用的是 local kg_count_

这导致一个细节问题:CN0 看到 KG=37 是热的(kg_count_[37] = 50),但 CN1 没看到(kg_count_[37] = 2),按本地权重,CN0 把 KG=37 提进 cohort,CN1 没提。两个 CN 跑 PlanCohorts 跑出来的 cohort 集不一样——即便 ArgmaxOwnerByAccess 现在一致,cohort 边界本身就漂移。

W18 plan consensus 把 vertex_weight 也升级成全 CN 聚合:

// AuraControlLoop.cc:568-604(节选)
absl::flat_hash_map<key_group_id_t, double> agg_kg_count;
if (AuraRuntime::PlanConsensusEnabled()) {
    agg_kg_count.reserve(cross_cn_kg_counts_.size());
    for (const auto& [kg, row] : cross_cn_kg_counts_) {
        double s = 0;
        for (auto v : row.per_cn) s += v;
        if (s > 0.0) agg_kg_count[kg] = s;
    }
    // self overlay:保持本地 kg_count_ 是最 fresh 的来源
    for (const auto& [kg, w] : kg_count_) {
        auto it = cross_cn_kg_counts_.find(kg);
        double self_in_matrix =
            (it != cross_cn_kg_counts_.end()) ? it->second.per_cn[self_cn_id_] : 0.0;
        agg_kg_count[kg] += (w - self_in_matrix);
    }
}
const auto& vertex_weight_in =
    use_consensus ? agg_kg_count : kg_count_;
auto proposed = PlanCohorts(vertex_weight_in, edge_ww_, edge_wr_, ...);

🌟 W16 + W18 一起开:四个 CN 跑 PlanCohorts,输入的 vertex_weight 是同一张全 CN 聚合表(差异只在 self 行用本地的最新值覆盖),输出的 cohort 集理论上几乎相同。这一步把”全 CN 对 cohort 边界达成一致”补完。

理论上几乎相同 ≠ 完全相同。两个 CN 看到的 cross_cn_kg_counts_ 在 heartbeat 周期内存在轻微 skew(不是同一个 100ms 窗口内的 heartbeat),所以 cohort 边界仍可能微弱差异。但通过 Jaccard 继承 + streak hysteresis 两层防抖(第 11 章 §2 / §5),最终 publish 的 confirmed cohort 集在稳态下确实是 4 CN 一致的。


7. 实测反直觉 —— 全局一致比独立宇宙吞吐还差

W15 / W16 / W18 全部上线后,跑 4-CN W=4 ablation 矩阵:

配置4-CN W=4 聚合 commit (KOPS, 中位数)vs baseline (185.24)
W7.4-B 独立宇宙(W14/W15/W16/W18 全关)189.28 (n=1)+2.18%
W14 only(cohort 稳定,W15/W16/W18 关)184.86 (n=2)−0.21%
W14 + W16(access summary,无 W15 派事务)184.34 (n=2)−0.49%
W14 + W16 + W18 plan consensus183.55 (n=2)−0.92%

🧠 三条曲线读出来的事

  1. 每加一层”协调机制”,吞吐就掉一点:W14 → −0.21%,再加 W16 → −0.49%,再加 W18 → −0.92%。
  2. W7.4-B 的 +2.18% headline 在 W14/W16/W18 上消失——本来”独立宇宙”挣的 2.18% 全部还给”全局一致”。
  3. W15 派事务在这条矩阵里没单独 ablation —— 因为 W15 的功能”把 NewOrder 改派给 home_cn”要 home_cn 不等于 self 才生效,而 W18 一致性下每 CN 只拥有 1/4 cohort,~75% 的 NewOrder 都该被 W15 派走,那条独立路径要单独的 cluster smoke 验证才能拿到数。

为什么”全局一致反而更差”?回到第 11 章 §7 那个分析的延伸——

🍎 再次借用银行比喻:W7.4-B 让 4 家银行各自当全能柜员,重复但是快——每家都能马上服务进门的客户。W14 + W16 + W18 把 4 家银行整成 4 家专科银行,全 CN 对”哪个 CN 是哪个 cohort 的专家”达成共识,但每个客户走进任一家都有 75% 概率”找错门”,需要被指引到对的门。

LOCAL surface area 的缩水:W7.4-B 每个 CN 的 OwnerLockTable 里都收纳了所有它看到的 hot KG,单 CN 自己跑的事务,acquire 大约 40% 命中本地。W18 一致性下,每个 cohort 只有一个真实 owner,自己跑的事务有 ~75% 概率走到一个 “non-self owned” cohort —— W14+W16+W18 下 LOCAL hit 实测 0.3%(第 11 章 §6)。

🌟 关键洞察W18 的吞吐 −0.92% 不是 bug,是设计目标错配的体现。W18 是为 §3.4 不变式 I1(同一 epoch 一 cohort 一个 authority)服务的;它是为吞吐 LOCAL hit 服务的。但跑 ablation 矩阵时 LOCAL hit 是直接可测的,I1 的”正确性”是难以挂数字的——所以工程上很容易把 W18 当性能项打分,得到反直觉结果

📑 修复方向:W15 派事务 + W7.7 RDMA REMOTE_OWNER takeover

W15 + W7.7(未来计划中的 fast RDMA remote-owner acquire)是把”找错门”的代价压下来的 lever:

当前路径(W14+W16+W18, 无 W15+W7.7):
  self 跑 txn → acquire 落在 remote-owned cohort → fallback to MN CAS
  延迟 ~10µs/CAS + MN atomic 拥塞

W15 路径(开 router):
  self 跑 txn → Resolve(write_kgs) → home_cn ≠ self
  → ExecuteTxn 派到 home_cn → home_cn 本地 LOCAL hit
  延迟 ~15µs RPC + 节省 MN atomic 一笔

W7.7 路径(RDMA fast remote takeover):
  self 跑 txn → acquire remote-owned KG → RDMA SEND 给 home_cn
  → home_cn 在 OwnerLockTable 短锁 → SEND 回 self
  延迟 ~5-8µs + 节省 MN atomic 一笔

W15 派事务 + W7.7 RDMA REMOTE_OWNER 都还没完整落 cluster smoke——这是 paper §6 把头牌数据停在 W7.4-B “+3.76%” 而不是”全开 +X%” 的原因。本章交代的数据是已实测落地的”全 CN 共识 + 无 fast remote takeover”的折损路径;下一章详细拆 +3.76% → +0.95% → +1.06% 这一系列折损 ablation 的工程教训。

🌟 结论一句话W15 / W16 / W18 这三条线把”跨 CN 一致性”做正确了,但代价是 LOCAL hit surface area 缩水到 0.3%。要在保持一致性的前提下把吞吐也拉回来,必须把”找错门”的远端 acquire 用 W15 派事务 + W7.7 RDMA fast takeover 压下来——这是 AURA 走完的下半场


✅ 自我检验清单

  • 两条独立路径:能解释 W15(事务级路由)和 W16/W18(跨 CN owner 共识)是两个并行问题,而不是同一路径的两阶段。
  • TransactionRouter::Resolve:能徒手写出 argmax 算法(每个 write kg 投一票,self tie-break);解释 Reason::kSelfCover / kRemoteBest / kNoData 各自的语义;记得 kNoData 命名是为了避开 <netdb.h>NO_DATA 宏。
  • ProxyTxnQueue 握手:能画出 OwnerRpcServer 网络线程 ↔ worker coroutine 的握手时序,包括 cv.wait_for 5s timeout 的边界;解释栈分配 + cv 同步 vs heap + 异步回调的工程权衡。
  • W15 走 TCP 不走 RDMA:能复现关键计算 —— RDMA slot 256 B(header 40 + payload 216),NewOrder 序列化 worst case ~450 B 装不下;理解”统一 TCP > 80% RDMA + 20% fallback”的路径切换代价权衡。
  • bootstrap bias:能解释为什么不修复 ArgmaxOwnerByAccess 会让”第一个广播的 CN 拥有所有 KG”;修复方法是 self overlay(用本地 kg_count_ 覆盖矩阵的 self 行)。
  • W18 plan consensus:能解释 W18 在 W16 之上加什么 —— 不仅 owner 一致(W16),cohort 边界也一致(vertex_weight 用全 CN 聚合而不是本地 kg_count_)。
  • 反直觉折损:能解释为什么”每加一层一致性吞吐就掉一点”是符合预期的——LOCAL surface area 缩水;修复方向是 W15 派事务 + W7.7 RDMA REMOTE_OWNER fast takeover。

📚 参考资料

概念入门

  • AURA paper § 3.5 (本仓库 paper_lock_ownership_atc_en/sections/3_design.tex):affinity router 的形式化设计;本章的 W15 实现是这一节的工程落地。
  • “RDMA over Converged Ethernet 2 (RoCEv2)” —— RFC 8260:单边/双边 RDMA 原语的延迟/带宽特性,是本章 §4 RDMA vs TCP 选型决策的背景知识。

关键论文

  • “Lotus: Locality-Sensitive Transactional Memory for Disaggregated DRAM” —— USENIX ATC 2024:W15 事务级路由的设计灵感来源;LOTUS 用 critical-field 静态指定 home,AURA 用 ArgmaxOwnerByAccess 动态决定。
  • “Eris: Coordination-Free Consistent Transactions Using Network Multicast” —— SOSP 2017:跨 CN 共识在数据通路上的另一种实现思路(用 in-network ordering 替代 access summary heartbeat),是 W16 设计上的对照点。

行业讨论

  • “You probably don’t need RDMA” —— Frank McSherry blog 2020:低延迟通信选型上”TCP 已经够快”的反方观点;本章 §4 走 TCP 的工程决策有同源思路。
  • “Bootstrap bias in distributed consensus” —— Tyler Treat blog 2018:多种共识协议的 bootstrap 阶段语义陷阱,与 W16 self overlay 修复的根因同类。

框架文档

  • TransactionRouter 源码:本仓库 CREST-aura-impl/CREST-Opensource-0007/src/transaction/aura/TransactionRouter.{h,cc} 完整实现。
  • ProxyTxnQueue 源码src/transaction/aura/ProxyTxnQueue.{h,cc} —— cv 握手的 MPMC 队列。
  • AccessSummary heartbeatsrc/transaction/aura/AuraControlLoop.cc:497-535(heartbeat 广播)+ :219-240(IngestAccessSummary 入口)+ :250-280(ArgmaxOwnerByAccess 的 self overlay)。
  • OwnerRpcRdma slot 限制src/transaction/aura/OwnerRpcRdma.h:158kSlotBytes = 256 常量定义。