13 KiB
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 重构成“两级调度”架构:
- global router 在所有 bucket 之间选择目标 bucket
- 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 形态:
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 模式 - 两者同时出现时直接报错,避免歧义
配置校验约束:
buckets非空- 每个 bucket
num_instances > 0 input_length_min <= input_length_max- bucket 区间不重叠
- bucket 排序后必须能唯一命中一个 bucket
- 旧模式与新模式互斥
运行时架构
运行时拆成三层:
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 摘要,不直接操作实例数组。
建议接口:
trait GlobalRouter {
fn name(&self) -> &'static str;
fn route_bucket(
&mut self,
req: &RequestRecord,
buckets: &[BucketView],
now: f64,
) -> GlobalRouteDecision;
}
BucketView 是只读摘要,至少包含:
bucket_idnameinput_length_mininput_length_maxnum_instancesqueue_len_sumqueue_len_maxkv_blocks_used_sumkv_blocks_total_sumactive_requestspredicted_prefix- 可选的
estimated_drain_time
predicted_prefix 表示该 bucket 对当前请求的 bucket 级 prefix 命中预测,用于让 global router 感知 bucket 级 cache affinity。
LocalRouter
local router 继续以 bucket 内 instance 池为输入。
建议接口保持和现有语义接近:
trait LocalRouter {
fn name(&self) -> &'static str;
fn route_instance(
&mut self,
req: &RequestRecord,
instances: &[Instance],
meta: &MetaStore,
now: f64,
) -> LocalRouteDecision;
}
现有 RouteDecision 需要拆成两层,最后再合并成对外统一的日志结构:
GlobalRouteDecisionLocalRouteDecisionRouteDecision:包含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
目标形式:
score = a * length_penalty + b * load + c * miss
设计意图:
- 能研究“非严格按 input length 分发”的收益或损失
- 同时保留长度匹配偏好,避免第一阶段就退化为完全无约束调度
暂不实现:
- 完全扁平的跨 bucket instance 全局打分
- per-bucket 特化 global scoring
- 跨 bucket 回退式 cache 共享
事件与 Driver 设计
当前 Event::BatchTick { instance } 假设 instance 是全局单层编号。多 bucket 后需要改成:
Event::BatchTick { bucket: BucketId, instance: InstanceId }
driver 主循环改为:
Arrival- 读取请求
- 调用
BucketedService::route_and_admit - 记录全局+局部路由决策
- 为
(bucket, instance)安排BatchTick
Sample 事件可以继续是全局事件,但采样时要遍历所有 buckets 的所有 instances。
inflight 结构保留按 req_id 索引,但 value 中新增:
bucketbucket_policylength_bucket_match- 可选的
bucket_predicted_prefix
Metrics 与可观测性
这次重构的重点之一是让 bucket policy 可研究,因此 metrics 必须明确区分 bucket 选择和 instance 选择。
routing_log.jsonl
建议新增字段:
global_modelocal_modechosen_bucketchosen_instancebucket_candidatesinstance_candidatesglobal_reasonlocal_reason
其中:
bucket_candidates记录每个 bucket 的摘要与分数instance_candidates记录选中 bucket 内的 instance 级候选信息
per_request.csv
建议新增字段:
bucketbucket_policylength_bucket_matchbucket_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 上产生可观测差异
迁移策略
为了控制风险,这次重构按以下顺序推进:
- 在配置层引入 bucket 结构与校验,但保留旧单 bucket 模式
- 把现有
Cluster收缩为单 bucket 语义 - 新增
BucketedService,先接通 strict bucket policy - 抽出
GlobalRouter接口,补上strict_input_length - 把现有 instance 级 router 适配为
LocalRouter - 扩展 driver / events / metrics 到
(bucket, instance)维度 - 再实现
bucket_score作为第一种非严格 bucket policy
这样可以先建立正确拓扑与日志,再逐步加入实验策略,避免一次性重写过多核心路径。
预期结果
完成后,simulator 将具备以下能力:
- 用显式 bucket 拓扑 replay 混合长度 trace
- 研究严格长度分桶是否带来收益
- 研究 global router 在 bucket 维度做非严格调度会产生什么影响
- 在 bucket policy 与 local instance policy 两个层次分别做 ablation
这为后续研究“bucket 是否必要”“bucket 边界怎么设”“global router 应不应该偏离长度分发”提供了清晰、可观测、可复用的 simulator 基础。