From 92d593d59b4ee213f22d316933bda0f5b5a77875 Mon Sep 17 00:00:00 2001 From: Gahow Wang Date: Fri, 17 Apr 2026 13:26:51 +0800 Subject: [PATCH] docs: add bucket-aware routing design --- .../2026-04-17-bucket-aware-routing-design.md | 449 ++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-17-bucket-aware-routing-design.md diff --git a/docs/superpowers/specs/2026-04-17-bucket-aware-routing-design.md b/docs/superpowers/specs/2026-04-17-bucket-aware-routing-design.md new file mode 100644 index 0000000..a497b7f --- /dev/null +++ b/docs/superpowers/specs/2026-04-17-bucket-aware-routing-design.md @@ -0,0 +1,449 @@ +# Bucket-Aware Routing Design + +## 背景 + +当前 simulator 只有一个全局 `Cluster`: + +- trace replay 的所有请求共享一组 `instances` +- router 直接在全局 instance 池里选目标实例 +- `meta_store`、L0/L1 cache 可见性、remote RDMA 也都是全局共享 + +这和目标架构不一致。目标架构要求: + +- service 内存在多个显式定义的 input-length buckets +- 每个 bucket 有独立的 instance 池,实例数由配置显式给出 +- bucket 之间的 cache / meta-store / remote 可见性严格隔离 +- router 不只是 bucket 内调度器,还要能以 global 视角感知 bucket 的存在 +- 后续需要用 simulator 研究: + - 是否应该区分 bucket + - 严格按 input length 分发与非严格分发的差异 + - bucket policy 与 bucket 内 instance policy 的耦合和收益 + +因此,这次重构不能把“先按 input length 选 bucket”硬编码到 service 层,而要把 bucket 选择纳入 global router 的决策面。 + +## 目标 + +本次设计的目标是把 simulator 重构成“两级调度”架构: + +1. global router 在所有 bucket 之间选择目标 bucket +2. local router 在选中的 bucket 内选择目标 instance + +同时满足: + +- bucket 由配置文件显式定义 +- 所有 bucket 共享同一套 local router 配置 +- bucket 之间完全隔离,不共享 L0/L1/meta-store/remote 视图 +- 保留足够的 metrics / routing log,支持后续研究 bucket policy 的效果 +- 尽量复用现有 `src/router/*` 中的 instance 级路由实现,避免把所有 router 改写成跨 bucket 扁平打分器 + +非目标: + +- 第一阶段不支持 per-bucket 自定义 router 算法 +- 第一阶段不支持跨 bucket cache 共享 +- 第一阶段不自动推导 bucket 边界或 bucket 实例数 +- 第一阶段不实现“全局扁平 instance 池”语义 + +## 方案比较 + +### 方案 A:service 层固定按 input length 选 bucket,router 只负责 bucket 内 instance + +优点: + +- 对现有代码侵入最小 +- local router 基本不用改 + +缺点: + +- 无法研究“router 不严格按照 input length 分发会怎样” +- bucket policy 被固化,无法与 instance policy 解耦对比 +- global 视角只能做观测,不能做真正决策 + +### 方案 B:两级路由,global router 选 bucket,local router 选 bucket 内 instance + +优点: + +- bucket policy 和 instance policy 清晰解耦 +- 符合目标架构,也适合做对照实验 +- 可以最大程度复用现有 router 实现作为 local router + +缺点: + +- 需要新增 service 层摘要视图与 global router 接口 +- driver / events / metrics 要显式携带 bucket 维度 + +### 方案 C:全局扁平 router,对所有 instance 跨 bucket 一起打分 + +优点: + +- 表面上最自由 + +缺点: + +- bucket policy 与 instance policy 混在一起,实验解释性差 +- 现有大多数 router 要重写 +- 容易把“bucket 是独立实例池”的物理边界冲淡 + +推荐方案:方案 B。 + +原因:bucket 是 service-level 拓扑与隔离边界,instance selection 是 bucket 内局部调度问题。这两个层次应该分开建模,否则后续无法清晰回答“bucket 本身是否有价值”和“instance 级路由算法是否有效”。 + +## 配置设计 + +`cluster` 从现在的单实例池配置扩展为显式 bucket 配置。 + +目标 YAML 形态: + +```yaml +cluster: + meta_store: + ttl_seconds: 1000.0 + router: + mode: cache_affinity + precise_probe_latency_us: 10.0 + precise_probe_topk: 4 + load_alpha: 0.1 + score_alpha: 1.0 + score_beta: 0.1 + prefix_k: 8 + affinity_fan_out: 0 + global_router: + mode: strict_input_length + length_penalty_weight: 1.0 + load_weight: 1.0 + cache_weight: 1.0 + buckets: + - name: short + input_length_min: 0 + input_length_max: 32768 + num_instances: 3 + - name: medium + input_length_min: 32769 + input_length_max: 81920 + num_instances: 4 + - name: long + input_length_min: 81921 + input_length_max: 131072 + num_instances: 3 +``` + +其中: + +- `cluster.router` 继续表示 bucket 内 local router 配置,全局统一 +- 新增 `cluster.global_router`,表示 bucket 选择策略 +- `cluster.buckets` 显式描述 service 拓扑 + +第一阶段建议继续兼容旧配置: + +- 若只提供 `cluster.num_instances`,则视为单 bucket 模式 +- 若提供 `cluster.buckets`,则进入多 bucket 模式 +- 两者同时出现时直接报错,避免歧义 + +配置校验约束: + +1. `buckets` 非空 +2. 每个 bucket `num_instances > 0` +3. `input_length_min <= input_length_max` +4. bucket 区间不重叠 +5. bucket 排序后必须能唯一命中一个 bucket +6. 旧模式与新模式互斥 + +## 运行时架构 + +运行时拆成三层: + +### 1. BucketedService + +新增一个 service 层对象,持有多个 bucket。每个 bucket 包含: + +- `bucket_id` +- bucket 配置 +- 独立的 `Cluster` + +`BucketedService` 职责: + +- 为请求构造所有 bucket 的摘要视图 +- 调用 global router 选择 bucket +- 将请求转发给选中的 bucket 内 `Cluster` +- 提供全量 bucket / instance 遍历接口,供 driver 做采样与 tick 调度 + +### 2. Cluster + +现有 `Cluster` 语义收缩为“单个 bucket 内的 cluster”: + +- 只持有该 bucket 的 `instances` +- 只持有该 bucket 的 `meta_store` +- 只运行该 bucket 的 local router + +`Cluster::route_and_admit` 不再负责 bucket 选择,只负责: + +- 在 bucket 内调用 local router 选 instance +- 执行该 bucket 内的 L0/L1/remote/miss 路径 +- 返回 bucket 维度补充后的 admission stats + +### 3. Router 分层 + +router 明确拆分为: + +- `GlobalRouter`:负责 bucket 选择 +- `LocalRouter`:负责 bucket 内 instance 选择 + +现有 `src/router/*` 中的大部分算法迁移为 `LocalRouter` 实现。 + +## Router 接口设计 + +### GlobalRouter + +global router 只看 bucket 摘要,不直接操作实例数组。 + +建议接口: + +```rust +trait GlobalRouter { + fn name(&self) -> &'static str; + fn route_bucket( + &mut self, + req: &RequestRecord, + buckets: &[BucketView], + now: f64, + ) -> GlobalRouteDecision; +} +``` + +`BucketView` 是只读摘要,至少包含: + +- `bucket_id` +- `name` +- `input_length_min` +- `input_length_max` +- `num_instances` +- `queue_len_sum` +- `queue_len_max` +- `kv_blocks_used_sum` +- `kv_blocks_total_sum` +- `active_requests` +- `predicted_prefix` +- 可选的 `estimated_drain_time` + +`predicted_prefix` 表示该 bucket 对当前请求的 bucket 级 prefix 命中预测,用于让 global router 感知 bucket 级 cache affinity。 + +### LocalRouter + +local router 继续以 bucket 内 instance 池为输入。 + +建议接口保持和现有语义接近: + +```rust +trait LocalRouter { + fn name(&self) -> &'static str; + fn route_instance( + &mut self, + req: &RequestRecord, + instances: &[Instance], + meta: &MetaStore, + now: f64, + ) -> LocalRouteDecision; +} +``` + +现有 `RouteDecision` 需要拆成两层,最后再合并成对外统一的日志结构: + +- `GlobalRouteDecision` +- `LocalRouteDecision` +- `RouteDecision`:包含 `chosen_bucket + chosen_instance + 两层 candidates` + +## Bucket 隔离语义 + +bucket 是显式物理隔离边界,不是逻辑标签。 + +必须满足: + +- 请求一旦进入 bucket,只能使用该 bucket 的实例池 +- L0 / L1 只在 bucket 内可见 +- `meta_store` 只描述该 bucket 内哪些实例持有块 +- remote RDMA 只允许从同 bucket 其他实例拉取 +- bucket 之间不共享 owner 信息 + +这保证了 simulator 中的 bucket 与实际服务拓扑有明确对应关系,避免 global router 虽然“知道 bucket”,但底层缓存模型仍然偷偷全局共享,从而污染实验结论。 + +## 首批 Global Bucket Policies + +第一阶段只实现两个 global bucket policy。 + +### 1. strict_input_length + +语义: + +- 只允许选择 `req.input_len` 所在区间的 bucket + +用途: + +- 作为严格长度分桶的基线策略 +- 对应最初目标架构图 + +### 2. bucket_score + +语义: + +- 对所有 bucket 计算分数并选择最优 bucket + +第一阶段分数只使用少量强信号: + +- `length_penalty`:请求长度偏离 bucket 目标范围的惩罚 +- `load`:bucket 总负载或最大负载 +- `miss`:bucket 级预测 miss,来自当前请求在该 bucket 上的 `predicted_prefix` + +目标形式: + +```text +score = a * length_penalty + b * load + c * miss +``` + +设计意图: + +- 能研究“非严格按 input length 分发”的收益或损失 +- 同时保留长度匹配偏好,避免第一阶段就退化为完全无约束调度 + +暂不实现: + +- 完全扁平的跨 bucket instance 全局打分 +- per-bucket 特化 global scoring +- 跨 bucket 回退式 cache 共享 + +## 事件与 Driver 设计 + +当前 `Event::BatchTick { instance }` 假设 instance 是全局单层编号。多 bucket 后需要改成: + +```rust +Event::BatchTick { bucket: BucketId, instance: InstanceId } +``` + +driver 主循环改为: + +1. `Arrival` +2. 读取请求 +3. 调用 `BucketedService::route_and_admit` +4. 记录全局+局部路由决策 +5. 为 `(bucket, instance)` 安排 `BatchTick` + +`Sample` 事件可以继续是全局事件,但采样时要遍历所有 buckets 的所有 instances。 + +`inflight` 结构保留按 `req_id` 索引,但 value 中新增: + +- `bucket` +- `bucket_policy` +- `length_bucket_match` +- 可选的 `bucket_predicted_prefix` + +## Metrics 与可观测性 + +这次重构的重点之一是让 bucket policy 可研究,因此 metrics 必须明确区分 bucket 选择和 instance 选择。 + +### routing_log.jsonl + +建议新增字段: + +- `global_mode` +- `local_mode` +- `chosen_bucket` +- `chosen_instance` +- `bucket_candidates` +- `instance_candidates` +- `global_reason` +- `local_reason` + +其中: + +- `bucket_candidates` 记录每个 bucket 的摘要与分数 +- `instance_candidates` 记录选中 bucket 内的 instance 级候选信息 + +### per_request.csv + +建议新增字段: + +- `bucket` +- `bucket_policy` +- `length_bucket_match` +- `bucket_predicted_prefix` + +`length_bucket_match` 用于直接衡量“最终 bucket 是否等于严格长度命中的 bucket”,是分析非严格分发影响的关键字段。 + +### instances.csv + +建议新增字段: + +- `bucket` + +### summary.json + +保留全局汇总不变,同时新增 bucket 维度 breakdown。可以采用: + +- 在 `summary.json` 内增加 `per_bucket` +- 或新增独立 `bucket_summary.json` + +第一阶段优先保证可读性与易分析,不需要过度抽象。 + +## 错误处理 + +需要显式处理以下失败场景: + +- 请求命中 0 个 bucket +- 请求命中多个 bucket +- bucket 配置不合法 +- 多 bucket 模式下事件找不到对应 `(bucket, instance)` +- routing log / metrics 缺失 bucket 信息 + +其中配置相关错误应尽量在启动时失败,而不是等到 trace replay 中途才暴露。 + +## 测试策略 + +测试分三层。 + +### 1. 配置测试 + +- 旧配置 `num_instances` 模式仍能成功加载 +- `buckets` 模式成功加载 +- bucket 区间重叠时报错 +- `num_instances` 与 `buckets` 同时配置时报错 +- bucket 未覆盖请求长度时报错或在运行时明确失败 + +### 2. Service / Driver 测试 + +- 短请求进入 short bucket +- 长请求进入 long bucket +- `bucket_score` 可以在长度不完全匹配时选择非默认 bucket +- long bucket 请求看不到 short bucket 的 meta-store / remote owner +- `(bucket, instance)` 维度的 `BatchTick` 能正确推进 + +### 3. 集成 Smoke Test + +构造 mixed trace,包含多个 input length 段与共享前缀模式,验证: + +- 严格 bucket policy 下,请求落入预期 bucket +- `routing_log` 同时记录 bucket 候选和 instance 候选 +- `per_request` / `instances` 带 bucket 字段 +- `bucket_score` 与 `strict_input_length` 在 mixed trace 上产生可观测差异 + +## 迁移策略 + +为了控制风险,这次重构按以下顺序推进: + +1. 在配置层引入 bucket 结构与校验,但保留旧单 bucket 模式 +2. 把现有 `Cluster` 收缩为单 bucket 语义 +3. 新增 `BucketedService`,先接通 strict bucket policy +4. 抽出 `GlobalRouter` 接口,补上 `strict_input_length` +5. 把现有 instance 级 router 适配为 `LocalRouter` +6. 扩展 driver / events / metrics 到 `(bucket, instance)` 维度 +7. 再实现 `bucket_score` 作为第一种非严格 bucket policy + +这样可以先建立正确拓扑与日志,再逐步加入实验策略,避免一次性重写过多核心路径。 + +## 预期结果 + +完成后,simulator 将具备以下能力: + +- 用显式 bucket 拓扑 replay 混合长度 trace +- 研究严格长度分桶是否带来收益 +- 研究 global router 在 bucket 维度做非严格调度会产生什么影响 +- 在 bucket policy 与 local instance policy 两个层次分别做 ablation + +这为后续研究“bucket 是否必要”“bucket 边界怎么设”“global router 应不应该偏离长度分发”提供了清晰、可观测、可复用的 simulator 基础。