450 lines
13 KiB
Markdown
450 lines
13 KiB
Markdown
# 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 基础。
|