Mooncake Store 源码阅读(四):Master 持久化、快照与热备恢复

Mooncake Store 把对象位置和副本状态集中在 Master。这样简化了一致性,但也意味着 Master 的状态不能只存在进程内存中。 本篇基于提交 777cc77,阅读 oplog、snapshot、standby controller 和恢复相关源码。 一、先澄清高可用目标 Mooncake Store 面向高速缓存池。缓存数据本身可以因为节点故障而丢失,系统并不等价于强持久化对象存储。 但控制面仍需要可靠:Master 重启后必须尽可能恢复对象元数据、Segment、配额和副本状态,否则即使真实字节还在,系统也不知道如何访问和回收它们。 因此需要区分两件事。 数据副本是否仍存在。 Master 是否记得这些副本及其状态。 本篇主要讨论第二件事。 二、为什么只做定期快照不够 假设 Master 每十分钟保存一次完整快照。第九分钟发生的大量 Put、Remove 和 Eviction,在崩溃后都会消失。 缩短快照间隔又会频繁扫描和序列化庞大元数据。 常见解决方案是 snapshot 加 oplog: Snapshot 保存某个时刻的完整状态。 OpLog 记录快照之后的增量变化。 恢复时先加载 Snapshot,再重放 OpLog。 Mooncake Store 的 Master 也采用了这一思路。 三、OpLog 记录什么 不是每个函数调用都值得写日志。需要持久化的是会改变可恢复状态的操作,例如对象元数据建立、提交、删除、副本变化和 Segment 相关更新。 当前 master_service.cpp 中可以看到 AppendOpLogWithDurableFinalize、批量预留和 durable finalize 等路径。 这里最关键的顺序是:某些不可逆的内存状态变化,要等对应日志达到持久条件后才能最终确认。 否则进程可能在“内存已经释放,但日志还没记住”时崩溃,恢复后重新得到一个指向已释放空间的副本。 四、Durable Finalize 的设计含义 名字里的 durable finalize 表明操作被拆成了准备与最终完成。 大致过程如下: 准备元数据变化 -> 追加 OpLog -> 等待达到持久条件 -> 执行最终回收或确认 这和 Put 的两阶段思路相似。系统先建立可回滚或可恢复的中间状态,再跨过明确的持久化边界。 ...

2026年8月24日 · 2 分钟 · Hellokitty

Mooncake Store 源码阅读(三):Get、副本选择与淘汰回收

写入建立对象,读取和淘汰决定缓存系统能否长期稳定运行。本篇沿 Get、Remove 与后台 Eviction 三条路径,观察 Mooncake Store 如何维护副本可读性和容量平衡。 源码基线仍为 777cc77。 一、Get 首先读取的是元数据 Client 不知道对象当前在哪台机器,也不应缓存一份永远不变的地址。 读取开始时,Client 向 Master 请求副本列表。核心入口之一是 MasterService::GetReplicaList。 Master 查找 key 对应的 ObjectMetadata,然后筛选当前允许读取的副本。返回结果包含 Segment、offset、长度和介质等信息。 随后 Client 才通过 Transfer Engine 把对象复制到本地 Buffer。 二、不是列表里的每个副本都可读 一个对象可能同时存在以下副本: 已完成并可读的内存副本。 正在复制的动态副本。 写入失败、等待清理的副本。 位于本地盘的副本。 所在 Segment 正在卸载的副本。 因此 Get 不能只检查 replicas 是否非空。源码中的 HasReadableReplica 以及各种状态判断,负责把控制面中的暂态副本排除掉。 读取正确性的一个重要来源,就是“只从已提交的副本集合中选择”。 三、副本选择优化什么 拥有多个可读副本后,系统还要选择来源。 理想选择通常考虑: 是否位于本机或同一故障域。 介质是 DRAM、VRAM 还是磁盘。 传输协议和拓扑成本。 副本是否正在被回收。 是否能满足本次 Buffer 类型。 Mooncake 将元数据筛选与实际传输分开,使选择策略可以持续演进。Master 保证候选集合合法,Client 和 Transfer Engine 完成具体数据路径。 四、读取失败不等于对象不存在 控制面返回副本后,数据面仍可能失败。例如节点刚好退出、网络断开,或 Segment 已经不可访问。 ...

2026年8月24日 · 2 分钟 · Hellokitty

Mooncake Store 源码阅读(二):Put 写入链路与对象原子性

Mooncake Store 的 Put 不是一次 RPC。它是一段跨越 Client、Master、分配器和 Transfer Engine 的两阶段流程。 本文继续使用提交 777cc77。阅读重点是 real_client.cpp 与 master_service.cpp 中的写入路径。 一、为什么 Put 必须拆成两段 如果 Master 收到 Put 请求后立刻把对象标记为可读,其他 Client 可能读到尚未传完的数据。 如果等所有数据都发送给 Master,再由 Master 落到目标节点,控制面又会成为数据面瓶颈。 Mooncake Store 的做法是:Master 先预留副本,Client 直接传输,最后由 Master 提交状态。 PutStart -> 返回目标副本 -> 直接传输对象 -> PutEnd 这相当于围绕一次数据面操作建立轻量级提交协议。 二、Client 侧的准备工作 Put 开始前,RealClient 需要确定源 Buffer 的位置和长度。源数据可能来自普通主机内存,也可能来自已经注册的加速器内存。 Client 会把大对象切成适合传输的任务。每个任务包含本地地址、远端 Segment、远端 offset 和长度,然后交给 Transfer Engine。 Store 不重复实现 RDMA、TCP 或 GPU Direct 细节。它只把“对象副本”翻译成 Transfer Engine 能执行的传输描述。 ...

2026年8月24日 · 2 分钟 · Hellokitty

Mooncake Store 源码阅读(一):从对象存储接口到控制面与数据面

Mooncake Transfer Engine 解决的是“怎样快速搬数据”,Mooncake Store 解决的则是更上层的问题:一个对象叫什么、放在哪些节点、有哪些副本、何时可读、空间不足时淘汰谁,以及 Master 重启后怎样恢复这些事实。 本文使用 Mooncake 仓库提交 777cc77 作为源码基线。这个版本的 Store 已经远超早期原型,包含多租户配额、内存与本地盘分层、动态副本、批量淘汰、快照和热备等能力。 一、先看整体分层 Mooncake Store 可以拆成四层。 第一层是上层调用者。vLLM、SGLang 或其他推理系统通过 Python、C++、Rust、Go 等接口访问对象。 第二层是 Client。RealClient 负责本地缓冲区、Master RPC、数据传输、重试和资源清理。 第三层是 Master。MasterService 保存对象元数据,管理 Segment、分配副本、维护租户配额,并驱动淘汰和复制。 第四层是数据资源。DRAM、VRAM、本地 SSD 或其他后端提供真正保存字节的空间,Transfer Engine 执行跨节点传输。 数据不会先发送到 Master,再由 Master 转发。Master 只告诉 Client 应该访问哪些副本,真正的大块数据直接在 Client 与目标内存之间移动。 二、源码目录怎样阅读 建议先抓住以下文件。 mooncake-store/include/real_client.h:Client 的主要状态和接口。 mooncake-store/src/real_client.cpp:Put、Get、Remove 与初始化流程。 mooncake-store/include/master_service.h:Master 的核心数据结构。 mooncake-store/src/master_service.cpp:元数据状态机和控制逻辑。 mooncake-store/include/replica.h:副本描述与状态。 mooncake-store/include/segment.h:可分配存储资源。 mooncake-store/src/allocation_strategy.cpp:副本放置策略。 mooncake-store/src/allocator.cpp:具体空间分配。 mooncake-store/src/transfer_task.cpp:Store 到 Transfer Engine 的任务封装。 不要一开始就顺序阅读体量巨大的 master_service.cpp。更有效的方法是从 RPC 接口出发,沿 PutStart、PutEnd、GetReplicaList 和 Remove 四条链路向下追踪。 ...

2026年8月24日 · 2 分钟 · Hellokitty

PCIe BAR 深入解析:从设备地址窗口到 DMA 与 GPUDirect Storage

在操作系统驱动、高性能网卡、NVMe SSD 和 GPU 系统中,经常会同时看到 BAR、MMIO、DMA、IOMMU、peer-to-peer DMA、GPUDirect RDMA 和 GPUDirect Storage 等概念。这些名词都与“设备怎样通过 PCIe 交换控制信息和数据”有关,但它们处在不同层次。 最简洁的理解是: BAR:让 CPU 能够定位并访问 PCIe 设备中的寄存器或显存窗口 DMA:让 PCIe 设备能够主动读取或写入系统内存 P2P DMA:让一个 PCIe 设备直接访问另一个 PCIe 设备暴露的地址空间 GDS:利用 DMA、GPU 内存映射和驱动协作,让存储数据尽量直接进入 GPU 显存 本文从 PCIe 地址空间和事务模型出发,解释 BAR 是如何分配和映射的,驱动为什么通过 BAR 下发命令,DMA 地址为什么不能简单等同于物理地址,以及 GPUDirect Storage 如何把这些机制组合成一条高性能数据路径。 一、先建立 PCIe 系统视图 一个典型服务器的 PCIe 拓扑如下: CPU │ Memory Controller │ Host RAM │ Root Complex ┌──────┴──────┐ │ │ PCIe Switch PCIe Endpoint ┌────┴────┐ GPU │ │ NVMe SSD NIC CPU 和内存构成主机侧。Root Complex 把 CPU/内存系统连接到 PCIe fabric。NVMe、网卡和 GPU 通常是 Endpoint。PCIe Switch 用于扩展端口和转发事务。 ...

2026年8月21日 · 10 分钟 · Hellokitty

Rust Tokio 深入解析:运行时原理、并发模型与最佳实践

Rust 语言只定义了 Future、async/await 和 Waker 等异步基础设施,并没有在标准库中提供完整的异步运行时。Tokio 补齐了这一层:它提供任务调度器、网络 I/O 驱动、定时器、异步同步原语、异步文件接口以及阻塞任务隔离机制。 如果把 Rust 异步程序比作一座城市: Future = 等待执行的工作 Executor/Scheduler = 安排工作在哪个线程运行 I/O Driver = 监听 socket 是否就绪 Timer Driver = 管理定时器何时到期 Waker = 通知某个任务可以继续 Tokio = 将上述组件组合成运行时 本文不再重复 Future::poll 和状态机的基础推导,而是聚焦 Tokio 本身:runtime 如何启动,任务如何调度,网络 I/O 为什么不会阻塞线程,以及生产代码中应该怎样处理并发、超时、共享状态、CPU 密集任务和优雅退出。 一、Tokio 提供了什么 一个典型 Tokio 应用依赖: [dependencies] tokio = { version = "1", features = ["full"] } full 适合学习和应用开发,但库作者通常应该只启用实际需要的 feature,缩短编译时间并减少依赖面。 Tokio 的主要组成包括: 组件 作用 Runtime 组合调度器、I/O driver 和 timer driver Task 由 runtime 调度的异步任务 tokio::net TCP、UDP、Unix Socket 等异步网络接口 tokio::time sleep、interval、timeout tokio::sync channel、Mutex、RwLock、Semaphore、Notify tokio::fs 文件系统异步接口 spawn_blocking 将阻塞或 CPU 密集工作移出异步 worker select! 同时等待多个异步分支 需要先建立一个重要认识: ...

2026年8月21日 · 6 分钟 · Hellokitty

Mooncake Transfer Engine 源码阅读(四):TENT 下一代架构如何重构传输引擎

Mooncake 仓库同时存在经典 Transfer Engine 和 TENT(Transfer Engine Next)。TENT 不是简单增加一种 Transport,而是重构了 Segment 生命周期、运行时调度、拓扑选择、QoS、故障转移和插件体系。 阅读版本: Mooncake commit: 777cc7782417b6e554cf7c2d53210d0d8f89f5cc 一、为什么需要下一代引擎 经典 TE 已经支持 RDMA、TCP、NVLink 和多种硬件,但随着后端增加,TransferEngineImpl + MultiTransport + 各 Transport 容易出现几个问题: Segment 生命周期分散在元数据和后端注册逻辑中; 传输选择主要依赖静态协议与局部规则; 多 rail、拥塞、故障和 QoS 难以统一调度; 不同后端各自维护进度线程和资源模型; 新硬件接入需要理解大量经典内部约定; 请求取消、deadline 和 failover 缺少统一运行时。 TENT 将这些能力上移到 Runtime 层。 二、目录结构 TENT 核心位于: tent/src/runtime/ ├── transfer_engine_impl.cpp ├── segment.cpp ├── segment_manager.cpp ├── segment_registry.cpp ├── segment_tracker.cpp ├── transport_loader.cpp ├── transport_selector.cpp ├── progress_worker.cpp ├── admission_queue.cpp ├── qos_contract.cpp ├── receiver_credit.cpp ├── topology.cpp ├── control_plane.cpp └── proxy_manager.cpp 外围包括: ...

2026年8月21日 · 3 分钟 · Hellokitty

Mooncake Transfer Engine 源码阅读(三):RDMA、TCP 与请求完成状态机

Transfer Engine 的公共 API 很统一,但 RDMA 和 TCP 的实现差异很大。RDMA 需要 MR、QP、WR 和 CQ;TCP 需要连接、lane、消息 framing 和接收端主动拷贝。本文沿源码比较两条路径,并分析 Batch 状态如何完成。 阅读版本: Mooncake commit: 777cc7782417b6e554cf7c2d53210d0d8f89f5cc 一、Transport 抽象 Transport 基类统一定义: install / uninstall registerLocalMemory / unregisterLocalMemory submitTransfer getTransferStatus allocateBatchID / freeBatchID 它还定义 TransferRequest、TransferStatus 和内部 BufferEntry。 公共抽象要求每个后端回答三个问题: 本地内存如何准备为可传输状态; 请求怎样排队和执行; 如何查询每个 task 的最终状态和字节数。 具体连接模型不属于公共 API。 二、RDMA 初始化 RDMA 后端主要位于: rdma_transport.cpp rdma_context.cpp rdma_endpoint.cpp endpoint_store.cpp worker_pool.cpp 安装阶段通常完成: 枚举 RDMA devices / ports / GID -> 创建每张 HCA 的 context -> 建立 PD、CQ 等资源 -> 启动 worker / poller -> 发布 NIC endpoint metadata -> 准备 endpoint store Mooncake 支持多 HCA、多端口和 GPU 内存,因此一个逻辑请求可能被切到多个 rail 上并行发送。 ...

2026年8月21日 · 3 分钟 · Hellokitty

Mooncake Transfer Engine 源码阅读(二):Segment、内存注册与元数据服务

Mooncake Transfer Engine 的核心抽象不是“远端指针”,而是 Segment + BufferDesc + Metadata。应用注册本地内存后,TE 将地址范围、设备位置和传输协议发布为 Segment 描述,其他节点才能定位并建立连接。 阅读版本: Mooncake commit: 777cc7782417b6e554cf7c2d53210d0d8f89f5cc 一、为什么不能直接发送指针 一个进程中的地址: 0x7f12... 在另一个进程中通常没有意义。即使两台机器都使用相同数值,它们也不指向同一块物理内存。 高性能传输还需要额外信息: 这是 CPU DRAM 还是 GPU VRAM; 对应哪张 GPU; 是否注册成 RDMA MR; rkey 和网卡 endpoint 是什么; 是否能用 NVLink、GPU IPC 或 CXL; 节点当前是否存活; 地址区间是否已经注销。 因此 TE 把地址空间包装成 Segment,并通过元数据服务交换描述。 二、SegmentDesc TransferMetadata::SegmentDesc 是远端发现的中心结构,主要承载: Segment ID Segment 名称 协议或协议集合 本机 RPC / endpoint 信息 BufferDesc 列表 设备与拓扑信息 后端扩展元数据 可以把一个 Segment 理解成某个 TE 实例公开的可传输地址空间: Segment: decode-node-7 ├── Buffer A: CPU metadata pool, protocol=rdma ├── Buffer B: GPU KV pool, protocol=rdma └── Buffer C: GPU KV pool, protocol=hip 同一块 GPU buffer 可以被多个后端注册,从而同时支持: ...

2026年8月21日 · 3 分钟 · Hellokitty

Mooncake Transfer Engine 源码阅读(一):统一传输 API 与整体架构

Mooncake Transfer Engine(简称 TE)是 Mooncake 数据平面的基础组件。它不负责决定 KV Cache 应该放在哪里,而是提供统一接口,把一段本地内存搬到远端 Segment,或者从远端 Segment 读取到本地内存。 本文基于 Mooncake 官方仓库: commit: 777cc7782417b6e554cf7c2d53210d0d8f89f5cc commit date: 2026-08-21 当前仓库同时保留经典 Transfer Engine 和下一代 TENT。前三篇先阅读经典实现,第四篇再分析 TENT 如何重构控制面、调度和传输后端。 一、源码布局 核心目录为 mooncake-transfer-engine/: 路径 作用 include/transfer_engine.h 对外 C++ API include/transfer_engine_impl.h 经典实现内部接口 include/transport/transport.h Transport 抽象、请求和状态 include/transfer_metadata.h Segment、Buffer 与元数据接口 src/transfer_engine.cpp 公共 API 转发层,同时兼容经典 TE 与 TENT src/transfer_engine_impl.cpp 初始化、内存注册、Segment 与批次管理 src/multi_transport.cpp 安装和选择具体 Transport src/transport/ RDMA、TCP、NVLink、NVMe-oF、EFA 等后端 tent/ Transfer Engine Next 实现 TE 的经典架构可以概括为: 应用 / Mooncake Store │ ▼ TransferEngine │ ▼ TransferEngineImpl ┌────┼──────────────┐ ▼ ▼ ▼ Metadata MultiTransport Batch 生命周期 │ ┌──────┼───────────────┐ ▼ ▼ ▼ ▼ RDMA TCP NVLink NVMe-oF ... 二、公共 API 是一层门面 TransferEngine 类本身很薄,大多数函数转发给 TransferEngineImpl: ...

2026年8月21日 · 3 分钟 · Hellokitty