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

13 KiB
Raw Blame History

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 形态:

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 摘要,不直接操作实例数组。

建议接口:

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 池为输入。

建议接口保持和现有语义接近:

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 目标范围的惩罚
  • loadbucket 总负载或最大负载
  • missbucket 级预测 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 主循环改为:

  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_instancesbuckets 同时配置时报错
  • 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_scorestrict_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 基础。