Mooncake Store 对象模型:对象、切片、副本和 Segment
梳理对象、切片、副本、Segment,以及 Master 与 Client 的职责,作为理解 Mooncake Store 读写流程的入口。
Mooncake Store 不是“Client 把值发给服务器,再由服务器保存”的中心化 KV 服务。它更像一个对象元数据控制面,再加上由 Client 和 Provider 执行的数据传输层。
读源码前先建立对象模型,否则 Put/Get 很容易看散。
主要入口:
mooncake-store/include/client_service.h
mooncake-store/include/types.h
mooncake-store/include/replica.h
mooncake-store/src/client_service.cpp
mooncake-store/src/master_service.cpp
layer view
Mooncake Store 的对象模型:控制面记录事实,数据传输层搬运内容
对象键是上层名称,切片描述本地缓冲区,副本才是落在 Segment 上的可读数据。Master 只维护这些关系,不参与大块数据传输。
调用方视角
上层看到对象键和本地缓冲区
Master 元数据
事实表和可见性
副本位置
真正能读写的位置
Client 数据传输层
根据元数据生成传输任务
对象不只是一个值
Store 对外暴露对象键,但内部不会假设一个值必须位于连续内存。写入时,调用方传入的是 Slice 列表:
Slice
ptr
size
这对 KV 缓存很重要,因为上层数据往往已经按 K/V、层、注意力头和页拆开。Store 不应该为了让抽象看起来整齐,就强行把它们复制到一块连续的大缓冲区中。
因此对象模型更接近:
ObjectKey
-> 对象元数据
-> 切片
-> 副本
-> Segment ID/名称
-> 偏移量
-> 长度
-> 状态
对象键是上层看到的名称;切片描述本次读写使用的本地缓冲区;副本则是 Store 内部真正可以读写的数据拷贝。
Segment 是可寻址存储空间
Segment 描述的是一段可被寻址的远端或本地存储空间。它包含:
Segment
id
name
base
size
te_endpoint
protocol
Store 里的副本不会直接指向“某台机器上的某个变量”,而是落在某个 Segment 的一段偏移和长度上。Transfer Engine 随后再把这段描述转换成 READ 或 WRITE 请求。
这层间接性很重要。Master 维护 Segment 和副本元数据,Client 根据元数据选择本地内存复制、Transfer Engine、NoF 或卸载路径。
副本状态决定数据是否可读
Replica 有状态。关键状态包括:
INITIALIZED
PROCESSING
COMPLETE
REMOVED
FAILED
一次 Put 过程中,Master 可以先分配副本,但这不等于数据已经写完。只有进入 COMPLETE 状态的副本才能成为正常读路径的候选。
因此,Store 的读取正确性不能简化为“对象键存在”,而要同时满足:
对象键存在
+ 元数据中有可用副本
+ 副本状态是 COMPLETE
+ 租约仍在有效期内
+ Client 能从对应 Segment 成功读出数据
这比普通 KV 服务复杂得多,但它允许大对象的数据传输绕开 Master。
Master 维护权威元数据
Master 的核心职责是维护权威元数据,而不是搬运数据。
它至少要回答:
- 哪些 Client 和 Segment 已经挂载。
- 每个对象有哪些副本。
- 哪些副本正在写入。
- 哪些副本已经进入
COMPLETE状态。 - 哪些 Segment 正在排空或卸载。
- 租户配额、分配策略、本地热缓存和租约是否允许本次操作。
Put 时,Master 负责分配副本并推进状态;Get 时,Master 负责返回可读副本和租约。真正的数据内容不经过 Master。
Client 负责组织数据传输
Client 不是一层简单的 RPC 包装。它同时持有:
MasterClient
TransferSubmitter
TransferEngine
LocalHotCache
Client 先向 Master 请求元数据,再根据元数据选择数据面的具体操作。例如,同一个端点内可以直接复制内存,远端内存副本通过 Transfer Engine 传输,文件或卸载存储则走对应路径。
这就是 Mooncake Store 的基本分层:
Master:
管理元数据、状态、租约和空间分配
Client:
向 Master 查询事实
调 TransferSubmitter 搬数据
Transfer Engine:
只处理 Segment/offset/length 的字节传输
ReplicateConfig 决定副本策略
ReplicateConfig 不是装饰参数。它会影响 Put 时 Master 如何放置副本。
常见字段包括:
replica_num
nof_replica_num
preferred_segments
preferred_nof_segments
prefer_alloc_in_same_node
with_soft_pin
with_hard_pin
对于 SGLang/Mooncake 集成,还要额外注意版本边界:SGLang 的 Mooncake 后端会探测已安装的 Mooncake Python 包是否支持 ReplicateConfig.group_ids。如果支持,它可以把同一个 KV Cache 页对应的 K 对象、V 对象和辅助内存池对象绑定到同一分组;如果不支持,就回退到普通的 batch_put_from 对象写入路径。
在本文参考的 Mooncake 稳定标签 v0.3.11.post1 中,ReplicateConfig 还没有 group_ids 字段。因此,不能把分组语义当作该版本 Store 的基础能力;它更像是 SGLang 适配层为不同 Mooncake 包版本预留的兼容路径。
和 HiCache 的映射
SGLang HiCache 接入 Mooncake Store 时,不会把“一个请求”整体作为对象,而是把前缀 KV 按页拆开:
HiCache page key
-> Mooncake 对象键
-> Store 对象元数据
-> 副本
-> Segment + 偏移量 + 长度
object mapping
从 HiCache 缓存页映射为 Store 对象
Mooncake Store 不知道请求和基数树。它只看到映射后的对象键,并为每个对象维护副本状态。
HiCache 逻辑页
SGLang 推理引擎中按页管理的缓存单元
Mooncake 对象键
Store 可寻址对象
Store 副本
可见性和位置
一个 MHA 缓存页通常对应 K/V 两个对象,MLA 通常只需要一个对象;混合内存池还会增加辅助状态对象。
所以 Store 看到的是一批对象,并不知道它们属于哪个请求,也不了解 SGLang 的基数树结构。这个边界必须守住。
把对象模型串起来
阅读 Mooncake Store 源码时,可以沿着一条逐层转换的链路理解:上层给出对象键和本地切片,Master 返回相应的副本信息,Client 再从副本信息中取得 Segment、偏移量和长度,最后由本地内存复制、Transfer Engine 或其他后端完成数据传输。
这条链里最容易混淆的是 Slice 和副本。Slice 描述调用方本次读写使用的本地缓冲区;副本则描述 Store 内部已经分配或提交的一份对象数据。它们会在 Put 或 Get 时配对,但不是同一个概念。
另一个关键点是状态。PROCESSING 副本说明 Master 已经预留位置,但不表示数据写入已经完成;只有 COMPLETE 副本才能被读取路径选择。只要记住这两个边界,后面的 PutStart、PutEnd、GetReplicaList 和租约就容易理解了。