# 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 基础。