Files
Easytier/plans/004-reuse-ospf-route-graph.md
T
fanyang 4b654fc56e perf(mpsc): sync send via noop_waker — +90% pps (249K → 474K)
The async fn Future state machine overhead (~1.9us) dominated
MpscTunnelSender::send, while RingSink operations were only ~40ns.

Breakthrough: make send() an async fn that completes synchronously
on the first poll for the direct (ring tunnel) path. Uses
futures::task::noop_waker() to construct a dummy Context, then calls
Sink trait methods (poll_ready, start_send, poll_flush) directly.
RingSink always returns Ready immediately, so the waker is never
invoked and the async fn completes without yielding.

Channel mode (TCP/UDP/WG tunnels) still uses async send_async()
with proper backpressure. Ring tunnels detected via tunnel_info()
type check in PeerConn.

Results (4 threads, 1400B, 15s):
  pps:                   249K → 474K (+90%)
  send_msg_by_ip:        3.53us → 1.67us (-53%)
  send_msg_internal:     2.40us → 502ns  (-79%)
  MpscTunnelSender::send: 1.97us → 144ns  (-93%)

All 207 peers:: tests pass. Netns-requiring tests (three_node,
credential) unchanged (require root).
2026-06-28 21:15:05 +08:00

8.7 KiB
Raw Blame History

计划 004:复用 OSPF route-table 构图以减少拓扑更新成本

执行者说明:按步骤执行本计划。每一步都必须运行验证命令,并确认结果符合预期后再继续。如果触发“STOP 条件”中的任一情况,立即停止并报告,不要自行发挥。完成后更新 plans/README.md 中本计划的状态行,除非 reviewer 明确说明由他们维护索引。

漂移检查(首先运行)git diff --stat 78146d16..HEAD -- easytier/src/peers/peer_ospf_route.rs easytier/src/tests 如果本计划写成后任何范围内文件发生变化,继续前必须对照“当前状态”中的摘录与实时代码;如果不匹配,按 STOP 条件处理。

状态

  • 优先级: P2
  • 工作量: M
  • 风险: MED
  • 依赖: plans/003-avoid-noop-ospf-route-rebuilds.md
  • 类别: perf
  • 计划生成于: commit 78146d16, 2026-06-18

为什么重要

每次 OSPF 拓扑更新当前会分别为 least-hop 和 least-cost route table 调用 build_from_synced_info。每次调用都会从 synced info 重新构建 peer graph,并重新构建 peer/CIDR indexes。对于 peer 数和 proxy CIDR 数较大的 mesh,这把一次拓扑变化放大成两次完整构图和多次 map/trie 重建。完成后应保持 route selection 结果不变,同时复用同一份 graph/materialized synced view,减少 CPU 和分配成本。

当前状态

  • easytier/src/peers/peer_ospf_route.rsupdate_route_table、graph builder、least-hop/least-cost map generation、CIDR trie rebuild 均在此文件。

当前代码摘录:

// easytier/src/peers/peer_ospf_route.rs:1628
// build next hop map
let (graph, start_node) =
    Self::build_peer_graph_from_synced_info(my_peer_id, synced_info, cost_calc);

// easytier/src/peers/peer_ospf_route.rs:1649
if matches!(policy, NextHopPolicy::LeastHop) {
    self.gen_next_hop_map_with_least_hop(&graph, &start_node, version);
} else {
    self.gen_next_hop_map_with_least_cost(&graph, &start_node, version);
};

// easytier/src/peers/peer_ospf_route.rs:1655
let mut new_cidr_prefix_trie = PrefixMap::new();
let mut new_cidr_v6_prefix_trie = PrefixMap::new();
// easytier/src/peers/peer_ospf_route.rs:2453
fn update_route_table(&self) {
    // ...
    self.route_table.build_from_synced_info(
        self.my_peer_id,
        &self.synced_route_info,
        NextHopPolicy::LeastHop,
        calc_locked.as_ref().unwrap(),
    );

    self.route_table_with_cost.build_from_synced_info(
        self.my_peer_id,
        &self.synced_route_info,
        NextHopPolicy::LeastCost,
        calc_locked.as_ref().unwrap(),
    );
}

仓库约定:core routing behavior must be preserved。先添加 characterization tests,再重构;不要在同一计划里改 route policy。

需要使用的命令

Purpose Command Expected on success
Format cargo fmt --all -- --check exit 0
Lint cargo clippy --all-targets --features full --all -- -D warnings exit 0, no warnings
Targeted tests cargo test --package easytier peer_ospf_route --features full -- --nocapture exit 0; route characterization tests pass
Archive tests cargo nextest archive --archive-file tests.tar.zst --package easytier --features full exit 0

临时目录约定

  • 临时文件、scratch 目录和 disposable worktree 必须放在 $HOME/tmp 下。
  • 如果 $HOME/tmp 不存在且本计划需要临时空间,先创建它。
  • 不要把临时产物放进被修改仓库。

范围

范围内(只能修改这些文件):

  • easytier/src/peers/peer_ospf_route.rs
  • easytier/src/tests/*(仅新增/调整 route-table characterization tests

范围外(即使看起来相关也不要触碰):

  • Route protocol schema and wire format。
  • Credential/trust semantics。
  • Peer center、foreign network manager、data-plane tunnels。
  • Any UI or config surface。

Git 工作流

  • Branch: advisor/004-reuse-ospf-route-graph
  • Commit message style follows existing conventional commits, for example refactor: introduce HedgeExt for task hedging; rewrite NatDstQuicConnector.
  • Do NOT push or open a PR unless the operator instructed it.

步骤

步骤 1:添加 route-table characterization tests

在修改实现前,新增测试覆盖至少一个包含以下元素的小拓扑:

  • 本 peer、两个 reachable peers、一个 unreachable 或 outdated peer。
  • 至少一个 IPv4 proxy CIDR 和一个 IPv6 proxy CIDR。
  • least-hop 和 least-cost 结果不同或至少都被断言。

测试应断言当前 route_tableroute_table_with_cost 对 peer next-hop、peer reachability、CIDR lookup 的结果。优先使用现有测试 helper;如果内部 API 不便,添加 #[cfg(test)] helper,不改变生产 API。

验证cargo test --package easytier peer_ospf_route --features full -- --nocapture → exit 0;新增 characterization tests 在重构前通过。

步骤 2:抽出一次性 graph build 输入

peer_ospf_route.rs 中把 build_from_synced_info 内部的 graph construction 拆成私有 helper,例如:

  • build_peer_graph_from_synced_info(...) 已存在则复用。
  • 新增 small struct 持有 graphstart_nodeversion 和后续 index rebuild 需要的 synced snapshot references。

不要改变 gen_next_hop_map_with_least_hopgen_next_hop_map_with_least_cost 的算法。

验证cargo test --package easytier peer_ospf_route --features full -- --nocapture → exit 0。

步骤 3:让 update_route_table 对两种 policy 复用 graph

update_route_table 改为在持有 cost_calculator read lock 时构建一次 graph/materialized input,然后分别对 self.route_tableself.route_table_with_cost 应用 least-hop / least-cost generation。

如果现有 RouteTable::build_from_synced_info 是唯一封装点,可以新增一个 sibling method,例如 build_from_prebuilt_graph(...),保持旧方法用于兼容 tests 或其他调用方。

验证cargo test --package easytier peer_ospf_route --features full -- --nocapture → exit 0characterization tests 仍通过。

步骤 4:避免重复构建共享 indexes

检查 build_from_synced_info 中 peer info map、IPv4 map、CIDR tries 的生成是否依赖 policy-specific next-hop map。如果只依赖 reachability 或 synced info,可移动到共享 helper;如果依赖每个 RouteTable 自己的 next_hop_map,不要强行共享,避免改变 semantics。

允许分阶段收益:只共享 graph build 也可完成本计划;共享 CIDR/index 只有在 characterization tests 能证明 behavior 不变时才做。

验证cargo test --package easytier peer_ospf_route --features full -- --nocapture → exit 0。

步骤 5:运行完整相关门禁

验证

  • cargo fmt --all -- --check → exit 0。
  • cargo clippy --all-targets --features full --all -- -D warnings → exit 0。
  • cargo nextest archive --archive-file tests.tar.zst --package easytier --features full → exit 0。

测试计划

  • 新增 route-table characterization tests,先在重构前证明现有行为,再在重构后保持通过。
  • 测试覆盖 least-hop、least-cost、CIDR lookup、unreachable peer exclusion。
  • 如果可行,加入一个轻量 counter/helper 在 test-only path 确认 graph builder 调用次数从 2 降为 1;如果这需要侵入生产代码,则不要做。

完成标准

  • update_route_table 不再对同一 synced topology 构建两次 peer graph。
  • least-hop 和 least-cost route outputs 与 characterization tests 中的旧行为一致。
  • 没有改变 routing protocol、credential trust 或 config behavior。
  • cargo fmt --all -- --check exits 0。
  • cargo clippy --all-targets --features full --all -- -D warnings exits 0。
  • cargo nextest archive --archive-file tests.tar.zst --package easytier --features full exits 0。
  • 没有修改范围外文件。
  • 已更新 plans/README.md 中本计划的状态行。

STOP 条件

  • plans/003-avoid-noop-ospf-route-rebuilds.md 未完成,且当前 route rebuild trigger 仍会对 no-op payload 重算。
  • 复用 graph 需要改变 least-hop 或 least-cost algorithm。
  • 现有代码让 cost_calc 在两次 build 之间发生有意状态变化;如果确认 begin_update/end_update 依赖两次独立 build,停止并报告。
  • Characterization tests 无法稳定构造 route-table expected outputs。

维护说明

  • reviewer 应重点审查是否在锁持有期间引入更长 critical section。
  • 未来新增 route policy 时应复用本计划抽出的 prebuilt graph input,而不是再调用完整 build_from_synced_info
  • 本计划不拆分 peer_ospf_route.rs 大文件;只做局部性能重构。