You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
let permit = self.concurrency.clone().acquire_owned().await?;
let store = Arc::clone(&self.store);
let key = key.to_owned();
tokio::task::spawn_blocking(move || {
let _permit = permit;
store.get(&key)
})
.await?
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
Single OpenViking RAGFS Distributed Cache Design
目录
背景与目标↑ 目录
本方案面向单 OpenViking 进程场景:
目标是在 RAGFS 基础上通过统一 Provider 接入 Yuanrong DataSystem、Mooncake 或
Redis,作为小文件和目录缓存层,提升多读少写场景下的读性能,同时保证单写者
场景下的缓存失效一致性。
本方案假设:
put/delete(key)成功后,同一 key 的后续读取不会返回旧值。时进入 bypass 并回源,不依赖异步 replica 提供写后读一致性。
在该前提下,一致性问题从分布式多写者一致性收敛为单写者缓存失效一致性。
技术选型↑ 目录
Openyuanrong-datasystem↑ 目录
总体定位:
利用计算节点的 HBM、DRAM 和 SSD,把共享存储中的数据按照访问需求搬到离计算最近的位置,通过共享内存、热副本和多级缓存降低延迟并削减后端存储负担。
图中 RAGFS 通过 SDK 访问本节点 worker:同节点进程与 worker 之间可通过共享内存读写数据,worker 之间通过 TCP 或 RDMA 传输缓存对象;节点内或节点间的 HBM 数据可通过 HCCS、RoCE 等链路流动;ETCD 或内置 Metastore 负责集群元数据和节点管理。RDMA、RoCE、HCCS 等能力是否可用取决于实际部署版本、编译选项和硬件环境,应以部署版本的能力为准。
控制小对象带来的内存碎片:初始化时申请一大块共享内存,通过空闲块复用和相邻空闲空间合并减少碎片;系统本身不负责自动将多个 key 合并为一个对象。
一致性策略
Yuanrong DataSystem 的 KV 接口支持以下一致性类型,一致性类型通过
SetParam、MSetParam等写入参数指定:PRAMCAUSAL上述一致性类型解决的是 KV 操作的可见顺序,不直接提供文件系统层面的路径、文件内容和目录成员关系的一致性。RAGFS 仍需以文件 key、目录 key 和
subtree_generation等机制维护文件系统语义。本文后续缓存方案采用以下 key 级副本更新前提:
该机制保证同一个 key 更新完成后不会继续从旧副本读取旧值;跨 key 的原子关系,例如“文件内容更新并同步更新父目录项”,仍由 RAGFS 的变更顺序和失效规则保证。
可靠性与生命周期
none:对象只保存在缓存层,worker 故障或对象淘汰后可能丢失,适合可从 backend 重建的缓存数据。write_back:先写缓存,再异步持久化,写入延迟较低,但需要接受异步落盘窗口内的故障风险。write_through:写入缓存时同步持久化,可靠性较高,但写入路径更长。ttlSecond或Expire设置对象过期时间,适合限制缓存陈旧时间和回收冷对象。对于 RAGFS 缓存,缓存数据可以从 backend 重建,因此通常不把 Yuanrong 当作唯一持久化副本;具体写入模式应根据故障恢复目标选择。
KVClient 接口
RAGFS 的 native 接入层通过
KVClient使用以下主要接口:Init/ShutDownYuanrongProvider随 OpenViking 进程生命周期管理Create/MCreateSetMSetMSetTxGetBuffer和ReadOnlyBuffer;Buffer 形式可减少复制ReadDelExistExpireQuerySizeHealthCheckGet的subTimeoutMs可在 key 尚不存在或尚未就绪时等待一段时间,设置为0表示不等待。key 最长为 255 字节,并受允许字符范围约束,因此 RAGFS 应先对规范化路径编码或哈希,再生成稳定的缓存 key。参考资料:
Mooncake↑ 目录
Mooncake Store 是面向 LLM 推理和远程内存池的分布式 KVCache 存储引擎。Master Service 负责对象分配、元数据、lease、复制任务和集群管理,Mooncake Client 负责实际数据读写,控制流与数据流相互分离。
Mooncake 多节点缓存参考架构。版本、lease 和 invalidation 由 Rust 服务及外部控制面协同维护;Mooncake 原生数据面负责对象放置和直接数据传输。
图中每个应用节点包含进程内 L1 热缓存、Rust 服务和 Mooncake Client。Rust 服务负责缓存协议、版本判断和 L1 管理;Mooncake Client 通过 RPC 与 Master 交互控制信息,通过 Transfer Engine 在本地或远端 Segment 之间直接传输数据。控制面中的版本索引和失效事件是 RAGFS 接入时增加的文件系统语义层,不应视为 Mooncake 原生提供了订阅式 invalidation 总线。
Mooncake 控制小对象内存碎片的方式是预先分配较大的 Segment,通过空闲空间复用、相邻空闲空间合并和 bin 分类降低分配碎片;系统本身不负责自动将多个 key 打包为一个对象。
提供接口
Mooncake 公开的接口面包括 Store 原生客户端 API、C API、Python API 和 Rust bindings。Rust bindings 已进入官方版本,但具体方法签名仍应以实际 checkout 的版本为准。
new/setupsetup需要 hostname、metadata server、Segment 大小、协议、RDMA 设备和 Master 地址等参数health_checkput/getstring key -> object模型,不直接提供文件或目录语义BatchPut/BatchGetPutStart/PutEnd/PutRevokeExistKey/GetReplicaListRequestRemove/RemoveAllregister_buffer/unregister_bufferput_from/metrics、/query_key、/health等Mooncake 更适合作为 native dependency 接入,而不是把 Store 当作通用 HTTP 或 gRPC KV 服务。Rust 服务需要链接 Mooncake 原生库,并对同步或阻塞调用做线程隔离。
一致性策略
Mooncake 的公开一致性重点是对象 lease 和完整读取保护,而不是数据库式事务一致性:
ExistKey或GetReplicaListRequest成功后,读方获得对象级 lease。Remove、RemoveAll或 eviction 删除。Get失败,而不是返回可能损坏或只写入一部分的数据。PutStart到PutEnd之间的对象不会作为已完成对象使用;客户端异常退出后,Master 根据超时配置回收 zombie object。Mooncake 没有公开的 Store 级订阅式失效接口。因此图中的
version / lease / invalidation需要由 RAGFS 上层实现:文件内容使用版本化 key,当前版本保存在独立元数据中;更新时先写新对象,再切换版本索引,最后通过外部事件通道通知其他节点清理 L1。lease 只保证正在读取的旧对象不会被并发删除,不能代替版本切换和目录失效。数据面能力
key -> replica / segment / lease,Client 之间直接传输对象数据。preferred_segment、replica_num、soft pin 和 hard pin 控制副本放置与热点驻留。Mooncake 的公开性能数据主要面向较大的 KV block 和高并发传输,不能直接等价为 4 KB 或 16 KB 小文件的 P99。用于小文件缓存时,应优先采用 packed blob 和 side index,减少对象级元数据、lease 和网络调用放大。
Mooncake Client 也可以嵌入业务进程。该模式的代价包括:
参考资料:
Redis↑ 目录
Redis 是成熟的内存型网络 KV 数据库,可直接作为 RAGFS 的远程缓存 Provider。
应用通过 RESP 协议和现成 Rust 客户端访问 Redis Server,不需要链接 C++ native
库,也不需要在 OpenViking 进程内初始化 RDMA、共享内存或远程 Segment。
KV 缓存能力
Redis 提供的能力与文件系统缓存对象较容易映射:
GET/SET/DELCacheEnvelopeMGET/MSET、PipelineEXPIRE、SET EX/PX、TTLSET NX/XX、WATCHMULTI/EXECmaxmemory、LRU/LFU/TTL 等淘汰策略Pipeline 只减少多条命令的网络往返,并不自动提供事务原子性。Redis Cluster 中,
MGET、Lua 或事务涉及的多个 key 通常需要落在同一 hash slot;若文件 key、父目录key 和 generation key 需要原子操作,应使用一致的 hash tag,或者继续采用本文的
“backend 成功后逐 key 失效”方案。
一致性策略
单个 Redis 实例按顺序执行命令,单条命令具有原子性;Lua、Redis Functions 和
MULTI/EXEC可把一组受支持操作作为不可被其他命令穿插的执行单元。但 Redis主从复制默认是异步的,因此 primary 返回写成功时,replica 可能尚未收到该写入。
高可用部署下需要明确以下边界:
WAIT可以等待指定数量 replica 确认收到此前写入,降低故障切换后的丢写概率,但官方文档明确说明它不会使 Redis 成为强一致系统。
WAITAOF还可以等待本地或 replica 将写入同步到 AOF,提高持久性,但超时、故障切换和未覆盖全部成员时仍可能丢失数据。
新 primary 可能缺少刚刚确认的缓存写入。
backend 状态。发生 failover、超时或连接重置时,Provider 应短暂 bypass,并让
CachedFileSystem回源重建。在本文单 OpenViking 写者场景中,不要求 Redis 提供跨文件系统 key 的强事务。
CachedFileSystem仍负责先完成 backend 变更,再删除或更新文件 key、父目录 key和
subtree_generation。Redis 只保证每个缓存原语的执行,不替代文件系统一致性层。可靠性与高可用
Redis 可以按缓存的重要程度选择不同可靠性配置:
的缓存数据。
appendfsync everysec通常在性能与恢复窗口之间折中,
always更可靠但增加写延迟。操作、迁槽和客户端重定向的复杂度。
缓存容量建议设置
maxmemory,并使用allkeys-lfu或allkeys-lru淘汰策略。所有缓存对象仍应携带逻辑过期信息;即使 Redis TTL、淘汰或持久化恢复行为异常,
CachedFileSystem解码CacheEnvelope后也能拒绝过期或 generation 不匹配的数据。适用性
Redis 的主要优势是部署成熟、客户端生态完整、运维工具丰富,适合快速落地、普通
以太网环境和中小规模小文件缓存。它的限制是每次访问通常经过 socket、RESP 编解码
和数据复制,无法直接获得 Yuanrong 的同节点共享内存路径或 Mooncake 的
RDMA/注册内存数据面;大量小请求必须使用连接池、Pipeline 和
MGET控制 RTT。第一阶段可将 RedisProvider 映射为:
RedisProvider 应复用总体架构中的
CacheProvider接口,一致性、缓存对象格式、inflight 合并和 backend 回源逻辑不应在 Redis adapter 中重新实现。
参考资料:
关键维度对比↑ 目录
WAIT/WAITAOF提高安全性但不提供强一致replica_num、preferred_segment、soft pin、hard pin,提供显式 locality control与 Alluxio、JuiceFS 等系统相比,三者都不是带 POSIX、FUSE 或目录树语义的完整
文件系统缓存产品。它们只提供不同形态的 KV 缓存和数据面能力;目录树、rename、
版本索引、批量失效、回源和命名空间隔离仍需由 RAGFS 实现。
UB 支持情况↑ 目录
UB 定位及协议边界
灵衢(UnifiedBus,UB)面向超节点互联,将 I/O、内存访问和不同处理单元之间的通信统一到同一互联体系中。对分布式缓存而言,UB 主要优化跨节点或跨处理单元的数据搬移路径,不改变 KV 对象模型、缓存生命周期或上层一致性语义。
Mooncake 中需要区分两个名称相近但用途不同的协议:
ubUbTransportubshmemUBShmemTransportubshmem的能力不能用于推断ub的能力。本文对 Mooncake 的分析主要指通过protocol="ub"启用的 URMA 数据面。openYuanrong DataSystem
openYuanrong DataSystem 已集成 UB 支持,通过 URMA 在具备 UB 硬件的超节点内加速 worker 之间的数据传输,并对应用保持原有
KVClient接口。启用 UB 后,Set/Get 等调用方式不变,变化发生在 worker 的底层跨节点传输路径。主要能力和部署要求如下:
build.sh -M on启用 UB;如不需要 CANN 异构能力,可同时使用-X off。dscli start ... --enable_urma true启用。global.performance.enableUrma: true,并将宿主机 URMA 工具和/lib64/urma/挂载到 Pod。生产环境还建议关闭 LPI、启用大页内存并绑定 NUMA 节点,减少 CPU 低功耗状态和远端 NUMA 访问对尾延迟的影响。例如进程部署时可以组合使用:
Yuanrong 的 UB 文档已覆盖源码编译、进程部署、Kubernetes 部署和跨节点验证流程,部署路径相对完整。实际性能仍取决于 UB 拓扑、URMA 版本、NUMA 绑定、大页配置以及目标硬件环境。
Mooncake Store
Mooncake 已为经典 Transfer Engine 实现基于 URMA/UMDK 的 UB 后端,并接入 Store 的 Put、Get、副本写入和副本读取路径。上层没有新增 UB 专用的 Put/Get API,而是在通用初始化接口中指定
protocol="ub"和设备列表。Mooncake UB 当前支持:
READ和WRITE。构建时需要显式启用:
目标环境主要是 Kunpeng 950、openEuler 24.03 LTS-SP3、UBCore/URMA 和 UMDK。部署前需要确认存在真实的
liburma.so、URMA 头文件,并能通过urma_admin -l发现设备。当前构建逻辑在找不到真实liburma.so时可能编入 Mock URMA,因此“编译成功”不等于二进制已经具备真实 UB 硬件访问能力。Mooncake Store 初始化时建议显式指定真实设备名,例如
urma0或urma0,urma1。多设备路径会按 Slice 进行拓扑和失败重选,但不能仅凭配置多个设备就认定单次传输一定能获得所有设备的聚合带宽。当前限制包括:
OBMM_ENDPOINT尚未实现,实际可用的是 URMA Endpoint。ub路径主要支持 CPU/NUMA 内存;Ascend Fabric Memory 应使用ubshmem。READ和WRITE。因此 Mooncake UB 已经贯通设备发现、内存注册、元数据交换、连接建立、单边读写和 Store Put/Get,不只是协议占位;但它仍是默认关闭、面向特定硬件的高级传输后端,成熟度和测试覆盖低于 TCP/RDMA。
构建、部署与能力对比
UbTransportKVClientSet/Get 等接口保持不变build.sh -M on;运行时--enable_urma true-DUSE_UB=ON;初始化时protocol="ub"参考资料:
总体架构↑ 目录
RAGFS 将缓存拆分为“文件系统公共缓存层”和“可替换缓存 Provider”两部分。
CachedFileSystem只实现一次文件与目录缓存语义,Yuanrong、Mooncake 和 Redis 仅在执行get、put、delete等缓存原语时产生差异。整体调用关系如下:
CachedFileSystem负责:file_key、dir_key,编码和校验统一的CacheEnvelope。subtree_generation失效。Provider 只提供最小缓存原语:
Provider 对公共操作的映射:
getKVClient::GetMooncakeStore::is_exist+getGETputKVClient::SetMooncakeStore::putSET,可附带PXdeleteKVClient::DelMooncakeStore::removeDELget_manyGetbatch_get_into,第一阶段可循环getMGET或 Pipelinehealth_checkKVClient::HealthCheckMooncakeStore::health_checkPINGYuanrong 的 TTL、事务式
MSetTx、partial read,Mooncake 的ReplicateConfig、buffer registration、batch zero-copy,以及 Redis 的 Pipeline、Lua、WAIT等高级能力不进入最小公共接口。Provider 通过ProviderCapabilities声明能力,公共层只在能力可用时启用对应优化,不支持时回退到基础操作。配置只负责选择 Provider:
启动时由
CacheProviderFactory创建一个进程级Arc<dyn CacheProvider>,所有允许缓存的 mount 共享该实例。第一阶段不支持运行中热切换;修改provider后重启 OpenViking 生效,可以避免旧 Provider 上仍在执行的请求、注册内存和 native handle 生命周期互相干扰。结论:
CachedFileSystem决定“如何缓存文件系统”,Provider 决定“缓存对象存放在哪里”,职责边界清晰。subtree_generation等一致性逻辑只在公共层实现,不分别复制到各个 Provider。spawn_blocking或专用线程池隔离;Redis 使用异步连接池并限制连接数和并发命令数。Ok(None),不能被当作系统故障。接入 Yuanrong 后架构↑ 目录
Yuanrong 以
CacheProvider实现接入,不作为新的文件系统 backend,也不在CachedFileSystem中增加 Yuanrong 分支。公共缓存层只依赖Arc<dyn CacheProvider>,由启动配置决定装配YuanrongProvider还是其他Provider。
接入过程分为八步:
完成后,OpenViking 上层接口以及 LocalFS、S3FS、SQLFS 等 backend 均保持
不变。Yuanrong 的类型、错误码和 native handle 只存在于 Provider 实现内部。
接入 Yuanrong 后的整体架构
第一步:建立 Yuanrong Provider 的 crate 和 feature 边界↑ 目录
先把公共缓存逻辑和 Yuanrong native 依赖分开:
在 workspace 中增加可选 feature:
ragfs核心不得暴露DsClient、KVClient、C++Status或 native handle。未编译
cache-yuanrong时,如果配置了provider: yuanrong,启动阶段应返回明确的配置错误;不能运行到第一次
get时才失败。这一阶段的验收标准是:关闭 feature 时 RAGFS 可以完全不链接 Yuanrong
动态库,开启 feature 后 Factory 可以看到
yuanrongProvider 构造器。第二步:准备 Yuanrong Worker 和 C++ SDK↑ 目录
首先部署 Yuanrong DataSystem 集群,并在运行 OpenViking 的节点上启动 Yuanrong worker。RAGFS 只连接本节点 worker,不直接管理 primary、replica 或 ETCD。
同时准备 Yuanrong C++ SDK 的以下构建输入:
先使用独立 C++ 示例连接本节点 worker,验证:
只有该示例稳定通过后再进入 Rust 接入,避免把 worker 部署、动态库链接和 FFI
问题混在一起排查。
第三步:实现稳定的 C ABI Bridge↑ 目录
RAGFS 是 Rust 项目,而 Yuanrong 提供 C++ SDK。不要让 Rust 直接绑定
std::shared_ptr、C++Status等复杂类型,应先增加一层稳定的 C ABI。新增底层 crate:
build.rs负责:C ABI 至少暴露:
C++ bridge 内部创建并持有 Yuanrong client:
接口映射关系:
C ABI 必须明确内存所有权:
这一阶段完成后,Rust 看到的只是普通 C 函数和 opaque handle,不感知 Yuanrong 的 C++ 类型。
第四步:实现 Rust FFI 和安全的 YuanrongClient↑ 目录
在
ragfs-yuanrong-sys/src/lib.rs中声明 unsafe FFI:再新增安全封装 crate:
YuanrongClient管理 handle、错误转换和并发控制:如果 Yuanrong SDK 是同步 API,每次调用通过
spawn_blocking隔离:Semaphore或固定大小 client pool 用于防止大量缓存请求占满 Tokio blocking pool。若 Yuanrong client 不是线程安全的,则每个 handle 同时只允许一个 SDK 调用,或者建立多个 handle 的 client pool。安全客户端还必须完成统一错误分类:
其中 miss 是正常控制流,不能计入 Provider 故障率。
第五步:实现公共 CacheProvider 接口↑ 目录
YuanrongProvider组合安全客户端,并实现总体架构中定义的公共接口:操作映射如下:
getKVClient::GetOk(None)putKVClient::SetdeleteKVClient::Delget_manyGetGethealth_checkHealthCheckYuanrong 的 TTL、partial read、事务式批量写等能力通过
ProviderCapabilities声明。CacheEnvelope、文件和目录 key、逻辑过期时间以及subtree_generation均由CachedFileSystem处理,不在 Provider 中重复实现。第六步:接入配置和 CacheProviderFactory↑ 目录
增加 Provider 配置:
Factory 只在启动时选择一次:
进程内所有可缓存 mount 共享同一个:
初始化或健康检查失败时:
第一阶段不支持运行时热切换 Provider。修改配置后重启 OpenViking,避免旧
native handle、阻塞任务和新 Provider 生命周期交叠。
第七步:由 CachedFileSystem 使用 Provider↑ 目录
RAGFSBindingClient的启动顺序调整为:mount 包装流程保持 Provider 无关:
对于不适合缓存的插件,可以在 mount 级别关闭:
完成该步骤后,
MountableFS仍然只调用dyn FileSystem,不知道内部是否启用了缓存。公共层保存的字段为:
读路径调用公共 Provider 原语:
变更路径也不感知 Yuanrong:
Yuanrong 自身负责 key 在 primary 和 replica 之间的数据一致性;RAGFS 公共层负责
文件系统语义上的 key 选择、写后失效、目录失效和回源顺序,两者不能混为一层。
第八步:完成关闭、降级和端到端验证↑ 目录
关闭时按以下顺序释放:
至少需要验证以下链路:
最终调用链:
经过以上八步,接入关系可以概括为:
接入 Mooncake 架构↑ 目录
Mooncake 与 Yuanrong 使用同一个
CacheProvider扩展点。区别仅在 Provider 内部:Mooncake 可以直接使用官方 Rust crate
mooncake_store,不需要额外编写C ABI bridge,但仍需链接 Mooncake Store 和 Transfer Engine 的 native 库。
接入过程分为九步:
第一步:固定 Mooncake 版本和部署形态↑ 目录
第一阶段固定一个经过验证的 Mooncake Git commit,不直接跟随
main。部署至少包括:
先使用 TCP 协议完成端到端验证,再在真实硬件环境切换 RDMA 或 UB。协议切换只
修改 Mooncake 配置,不改变 Provider 接口和 CachedFileSystem。
第二步:增加可选 crate 和 Cargo feature↑ 目录
新增独立 adapter crate:
依赖官方 Rust crate,并固定 revision:
若构建环境不允许 Cargo 拉取 Git 依赖,可将同一 revision 作为 source archive
或 submodule 固定在内部依赖仓库中。无论采用哪种方式,都要记录 Mooncake
commit、C++ native 库版本和 Rust crate 版本的对应关系。
第三步:构建并链接 Mooncake native 库↑ 目录
Mooncake Rust crate 是对 C/C++ Store API 的安全封装,
build.rs仍需找到native 头文件和库。使用 Mooncake CMake 构建:
在 RAGFS 构建环境中设置并校验:
除
mooncake_store和transfer_engine外,还要准备其依赖的 glog、gflags、numa、curl、ibverbs、xxhash、jsoncpp、CacheLib 相关库以及 C++ runtime。
CI 应增加一个
cache-mooncakenative 构建任务。链接检查失败时立即停止构建,不要在运行阶段静默降级。
第四步:定义 MooncakeConfig 并完成 setup↑ 目录
配置需要覆盖
MooncakeStore::setup的必要参数:实现强类型配置:
启动时按顺序执行:
hostname、协议、segment 大小或服务地址无效时直接返回 Provider 初始化错误。
第五步:封装同步 Rust API 和并发控制↑ 目录
Mooncake 官方 Rust API 是同步接口。
MooncakeStore可跨线程共享,但不能直接在 Tokio executor 线程上执行阻塞的远程传输:
每次操作先获取 permit,再进入
spawn_blocking:Provider 层统一施加
operation_timeout_ms。超时只停止等待,native 调用可能仍在blocking 线程中运行,因此并发上限必须保守设置,避免故障时累积不可取消调用。
第六步:实现 MooncakeProvider↑ 目录
MooncakeProvider实现与 YuanrongProvider 相同的公共接口:操作映射和注意点如下:
getis_exist、getOption<Bytes>putputReplicateConfig,成功后返回deleteremoveget_manyget/batch_get_intogethealth_checkhealth_checkget不应只调用get_size判断 miss,因为负值可能同时表示不存在或内部错误。建议先调用
is_exist,存在后再调用get;调用间对象被删除时,将第二次返回的not found 继续映射为
Ok(None)。Mooncake 当前 Rust 接口没有通用的对象级 TTL。Provider 应在 capabilities 中声明
native_ttl = false,由CacheEnvelope保存逻辑过期时间,读取过期对象时按 miss处理并异步
remove。put使用不可变对象语义:同一个 key 的新内容只在公共层完成 backend 变更和旧key 失效后写入。MooncakeProvider 不实现文件系统版本、目录 generation 或 rename。
第七步:注册到 CacheProviderFactory↑ 目录
在 Factory 中增加 feature-gated 分支:
create_mooncake_provider负责:公共层不得出现
if provider == "mooncake"。Provider 选择只发生在 Factory,高级能力通过
ProviderCapabilities判断。第八步:接入公共 CachedFileSystem↑ 目录
MooncakeProvider 创建后,挂载流程和 Yuanrong 完全相同:
文件和目录读:
写、删除和 rename:
因此从 Yuanrong 切换到 Mooncake 不修改
CachedFileSystem、backend plugin 或OpenViking API,只修改配置并使用包含对应 feature/native 库的构建产物。
第九步:分阶段优化并完成验证↑ 目录
第一阶段优先保证基础接口、错误映射和失效顺序正确:
第二阶段再启用 Mooncake 的零拷贝和批量能力:
注册内存必须封装成 RAII
RegisteredBuffer,确保 drop 时调用unregister_buffer。batch_get_into要求调用方预先知道对象大小并准备 buffer,可在
CacheEnvelope大小元数据稳定后再接入,不能为了批量读取破坏公共 value格式。
至少验证:
Mooncake 接入后的职责边界为:
接入 Redis 架构↑ 目录
Redis 使用与 Yuanrong、Mooncake 相同的
CacheProvider扩展点。公共CachedFileSystem、CacheEnvelope、文件与目录一致性、mount 包装和故障bypass均复用前文,本节只描述 Redis adapter 的实现差异。接入过程分为六步:
第一步:增加 Redis adapter crate 和 feature↑ 目录
新增纯 Rust adapter:
选择支持 Tokio、连接池、Cluster 和 TLS 的成熟 Redis Rust 客户端,并固定版本:
RedisProvider 不链接 native SDK,也不使用
spawn_blocking。关闭cache-redisfeature 时,配置provider: redis应在启动阶段返回明确错误。第二步:定义连接与部署配置↑ 目录
配置示例:
密码通过环境变量或 secret manager 注入,不直接写入配置文件。第一阶段建议从
standalone 或 Sentinel primary-only 模式开始;只有容量或吞吐确有需要时再启用
Cluster。
启动顺序为:
第三步:实现 RedisProvider↑ 目录
RedisProvider 直接把公共缓存原语映射到 Redis 命令:
getGETOk(None),其他错误分类为 timeout/unavailable/internalputSET key value PX ttldeleteDELget_manyMGET或 PipelineGETOption<Bytes>health_checkPING建议结构:
put优先使用单条带PX的SET,避免SET成功而PEXPIRE失败。Provider只处理 Redis key 前缀、命令调用、超时和错误映射,不解析文件路径或
CacheEnvelope。第四步:注册到 CacheProviderFactory↑ 目录
在既有 Factory 中增加一个分支:
create_redis_provider校验 feature 和配置,建立连接并完成PING,然后返回Arc<dyn CacheProvider>。后续 mount、读 miss 回填和写后失效全部沿用前文公共流程,不新增 Redis 专用文件系统分支。
第五步:处理 Redis 特有约束↑ 目录
Redis adapter 需要额外处理以下边界:
read_from_replica: false,避免异步复制延迟导致写后读到旧缓存。若启用 replica read,只能作为允许短暂陈旧的性能模式。
MGET要求 key 位于同一 slot。第一阶段可对不同 slot使用 Pipeline;需要原子多 key 操作时再为同一命名空间设计
{hash_tag}。MGET、Pipeline 和批量DEL设置 key 数与总字节上限,避免单个请求占满连接或产生超大响应。
MOVED/ASK、超时或 Sentinel/Cluster 选主期间触发短暂 bypass;恢复探测成功后再重新启用缓存。
成为缓存正确性的唯一依据。
WAIT/WAITAOF。若需要降低 failover 丢失失效命令的概率,可作为 RedisProvider 可选写策略,但应接受额外写延迟。
第六步:完成验证和上线配置↑ 目录
除公共 Provider 契约测试外,Redis 至少需要验证:
上线初期建议使用 primary-only、短命令超时、有限连接池、
allkeys-lfu或allkeys-lru,并监控 hit rate、命令 P99、连接池等待、超时、eviction、keyspace miss 和 bypass 次数。
Redis 接入后的职责边界为:
缓存对象↑ 目录
文件缓存↑ 目录
文件 key:
文件 value 将内容和元数据打包为一个整体对象:
单写者场景不需要额外维护
ver_key。文件内容一致性由file_key的set/delete和必要的subtree_generation校验保证。目录缓存↑ 目录
目录 key:
目录 value 缓存 backend 原始
read_direntries,而不是权限过滤后的最终结果:缓存 raw entries 的好处是同一份目录缓存可以服务:
ls(original)ls(agent)treeglobrm/mv前的 URI 收集权限过滤仍在 OpenViking / VikingFS 层执行。
子树 Generation↑ 目录
子树 generation key:
subtree_generation用于处理remove_all和目录 rename。即使只有单 OpenViking 进程,RAGFS 也不一定知道 Yuanrong 中残留了哪些子孙缓存 key,所以仍然需要它。建议按 OpenViking 语义边界设置:
这样读缓存时只需要校验少量 generation key。
缓存对象策略↑ 目录
RAGFS 需要在读写路径前执行
cache_policy(path, op, caller_context)。Yuanrong 适合缓存稳定值,不适合缓存动作、权限判断、锁状态和瞬时控制面。权限不确定的对象不能缓存权限过滤后的最终结果。如果 backend raw entries 本身也依赖调用者权限,该路径应该 bypass。
锁文件和控制文件必须强制 bypass。锁文件表达当前瞬时状态,控制文件通常带有操作语义或读副作用,不能用普通文件缓存语义处理。
NotFound负缓存需要谨慎。.abstract.md、.overview.md等文件可能稍后生成,默认不缓存 NotFound;确需保护 backend 时,只允许极短 TTL,例如 100ms 到 1s。单写者一致性保证↑ 目录
单 OpenViking 进程下,不需要复杂分布式一致性协议,也不需要多节点写锁。RAGFS 只需要维护三类失效:
推荐变更顺序:
如果 cache 维护失败,可以选择两种模式:
如果要保证进程重启后也不读旧缓存,关键变更建议使用严格模式,尤其是
delete、rename、remove_all。文件场景↑ 目录
文件读↑ 目录
保证:
文件写与更新↑ 目录
文件创建和覆盖写统一视为
write_file(path, data):保证:
如果更新的是
.abstract.md或.overview.md,直接更新对应file_key即可。tree(agent)/ls(agent)通过dir_key + mget(child .abstract.md)读取摘要,不再维护组合缓存。文件删除↑ 目录
保证:
文件 Rename↑ 目录
文件 rename 既包括同目录改名,也包括跨目录移动:
删除
new_path的原因是目标路径历史上可能存在旧缓存。rename 返回后,下一次读new_path应回源 backend 并重建缓存。目录场景↑ 目录
目录读↑ 目录
保证:
目录创建↑ 目录
保证:
目录删除↑ 目录
空目录删除:
递归删除:
保证:
目录 Rename↑ 目录
目录 rename 是整棵子树路径切换:
保证:
缓存击穿与请求合并↑ 目录
当大量并发请求同时读取同一个尚未缓存的文件或目录时,如果每个请求都独立回源,会形成缓存击穿:
单 OpenViking 进程可以在
CachedFileSystem内维护进程级inflight表,将相同 cache key 的并发 miss 合并为一次 backend 请求:inflight的 key 使用规范化后的file_key或dir_key。第一阶段只合并可缓存的小文件全量读取和read_dir,不合并不同 offset/size 的 range read。Leader 与 Follower↑ 目录
首个发现 cache miss 且成功创建
inflight[key]的请求成为 leader,负责回源和回填。后续读取相同 key 的请求成为 follower,等待 leader 的共享结果:目录读取使用相同机制:
inflight只合并正在进行的请求,不保存长期结果。请求完成后仍由 Yuanrong 承担正常缓存命中。与文件变更的并发关系↑ 目录
请求合并不能让较早开始的读请求在写、删除或 rename 完成后重新写入旧缓存。为此,RAGFS 在进程内为路径维护单调递增的
mutation_seq:mutation_seq必须在 backend 变更成功后立即递增,早于 Yuanrongset/delete,从而关闭“Backend 已变化但旧 leader 仍可回填”的竞态窗口。变更操作还需要让 affected key 的旧 inflight 条目失效;变更后到达的新读请求不能加入旧 leader,而应重新检查缓存并创建新一轮 inflight。inflight条目需要携带创建时的start_seq。follower 加入前和接受共享结果前都要比较当前mutation_seq:已经在变更前开始等待的请求与变更操作存在时间重叠,可以共享该轮读取结果;变更返回后新开始的请求必须看到新缓存或重新回源,不能复用旧 inflight 结果。
对于目录操作,
mutation_seq需要覆盖目录自身及受影响的父目录;remove_all和目录 rename 仍通过subtree_generation处理残留子孙缓存。mutation_seq用于阻止进程内正在执行的旧读取回填,subtree_generation用于使 Yuanrong 中已经存在的旧子孙缓存失效,两者职责不同。错误、超时与清理↑ 目录
leader 的成功和失败结果都需要通知所有 follower,并确保
inflight条目最终被删除:leader 必须设置 backend 请求超时,避免一个挂死请求长期占用
inflight。follower 可以设置等待超时,但等待超时后不应立即对同一个 key 再发起独立回源,否则会重新形成击穿;应返回超时错误,或者等待原 leader 的全局截止时间结束后再参与下一轮 leader 竞争。单个 follower 被取消不能取消 leader,因为 leader 可能仍被其他 follower 等待。只有 leader 自身达到全局超时、Backend 返回或系统关闭时,才结束该 inflight 请求。
容量与观测↑ 目录
inflight表需要限制最大 key 数,防止大量不同 key 的 miss 占用过多内存:建议记录:
请求合并只减少同一进程内对 Backend 的重复访问,不替代 Yuanrong 的分布式 key 一致性,也不改变现有文件和目录失效规则。
预取方案↑ 目录
预取不修改 Yuanrong worker 内部。它由 RAGFS 进程内
PrefetchExecutor调度,通过 Yuanrong SDK 向backend发起mget。预取触发↑ 目录
不要使用“每次读取都预取”,建议采用一次性窗口触发,推荐规则:
miss 表明当前访问集合可能尚未进入缓存,预取价值最高。
观察到同一目录连续读取两个文件,再预取后续文件。
read_dir miss 后预取少量高价值子文件,例如 .abstract.md。
同一目录或 trigger key 在 1~10s 内最多触发一次。
每次最多预取 4~16 个对象,并限制全局并发和字节数。
使用一次批量 mget,避免逐 key 查询。
submit_prefetch必须是 best-effort:热点反馈 + hot_keys 缓存↑ 目录
hot_keys只缓存热点 key 列表,不缓存真实数据,也不参与一致性判断。单进程场景做hot_keys收益不大,暂时不做。OpenViking 定制优化↑ 目录
1. 缓存 raw read_dir↑ 目录
目录缓存只缓存 backend 原始 entries,不缓存权限过滤后的输出。这样同一份缓存可以被多条 OpenViking 路径复用。
实际收益
2.
tree(agent)批量摘要读取↑ 目录tree(agent)和ls(agent)使用目录缓存与摘要文件缓存组合完成:这样目录 entries 与摘要文件分别走
dir_key和file_key,一致性更简单,也避免维护额外的组合缓存失效规则。3. write_context 批量更新↑ 目录
OpenViking 的
write_context通常一起写:在 backend 写成功后,使用 Yuanrong
mset_tx批量更新这些小文件缓存,并删除相关目录缓存:故障与降级↑ 目录
缓存层不能破坏文件系统正确性。
指标监控↑ 目录
需要记录:
推荐落地顺序↑ 目录
CachedFileSystem+CacheProvidertrait。cache_policy:排除锁文件、控制文件、权限敏感路径。.abstract.md/.overview.md。read_dir缓存。subtree_generation覆盖remove_all和目录 rename。inflight的缓存击穿保护和请求合并。write_context批量更新。PrefetchExecutor。hot_keys热点反馈和非侵入式预取。最终保证↑ 目录
在单 OpenViking 进程和 Yuanrong key 级一致性前提下,本方案保证:
一句话总结:
Beta Was this translation helpful? Give feedback.
All reactions