Files
kvcache-simulator/docs/superpowers/specs/2026-04-17-bucket-aware-routing-design.md

450 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 池”语义
## 方案比较
### 方案 Aservice 层固定按 input length 选 bucketrouter 只负责 bucket 内 instance
优点:
- 对现有代码侵入最小
- local router 基本不用改
缺点:
- 无法研究“router 不严格按照 input length 分发会怎样”
- bucket policy 被固化,无法与 instance policy 解耦对比
- global 视角只能做观测,不能做真正决策
### 方案 B两级路由global router 选 bucketlocal 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 基础。