Skip to content

RAGFS 缓存 ​

RAGFS 缓存是 OpenViking 的可选读缓存层,用于加速文件全量读取和目录读取。它只作为加速层,不作为事实数据源;数据仍以 backend filesystem 为准。

CachedFileSystem 适用前提:

  • 只有一个 OpenViking / RAGFS 进程写入同一 namespace。
  • 文件和目录变更都经过 RAGFS。
  • backend 不被外部绕过 RAGFS 直接修改。
  • 缓存 Provider 的同一 key 写入或删除成功后,后续读取不会返回旧值。

这些前提只适用于读缓存层。QueueFS 和 PathLock 也复用 CacheRuntime,但有各自的一致性规则。

快速开始 ​

首次配置仍建议先完成基础配置:

bash
openviking-server init
openviking-server doctor

然后在 ~/.openviking/ov.conf 中配置顶层 cache Provider,并在 storage.agfs.cachefs 选择 backend=cache:

json
{
  "cache": {
    "provider": "redis",
    "params": {
      "mode": "standalone",
      "endpoints": ["redis://127.0.0.1:6379"],
      "pool_size": 32,
      "connect_timeout_ms": 1000,
      "command_timeout_ms": 1000,
      "default_ttl_seconds": 3600
    }
  },
  "storage": {
    "workspace": "./data",
    "agfs": {
      "backend": "local",
      "cachefs": {
        "backend": "cache",
        "namespace": "openviking",
        "max_file_size_bytes": 1048576,
        "bypass_prefixes": ["/queue", "/tmp"]
      },
      "pathlock": {
        "provider": "cache",
        "namespace": "openviking",
        "lock_expire_secs": 30.0
      }
    }
  }
}

启动 Redis 和 OpenViking:

bash
redis-server

在另一个终端启动 OpenViking:

bash
openviking-server --config ~/.openviking/ov.conf

如果配置文件在默认路径 ~/.openviking/ov.conf,也可以直接运行:

bash
openviking-server

可用 Provider:

Provider适用场景备注
redis默认交付、普通网络环境内置于 RAGFS,支持 standalone、Cluster 和 Sentinel
dynamicYuanRong、Mooncake 或闭源缓存系统通过版本化 C ABI 从外部动态库加载

MemoryMockProvider 只用于单元测试和 smoke test,不是生产配置项。

Cache PathLock ​

将 storage.agfs.pathlock.provider 设为 cache,即可通过共享 Redis CacheRuntime 协调多个 OpenViking 进程的路径锁。pathlock.namespace 为必填项,同一 OpenViking 部署的所有进程必须使用相同值。Cache PathLock 只支持内置 Redis Provider。独立部署应使用不同 namespace;分布式路径锁不会解除 CachedFileSystem 的单写者前提。

Redis HASH key 按逻辑路径 scope 拆分:

text
ov:pathlock:{namespace}:global:tokens
ov:pathlock:{namespace}:scope:_system:tokens
ov:pathlock:{namespace}:scope:account:{account}:tokens

{namespace} 是 Redis Cluster hash tag,因此同一部署的 PathLock key 仍位于同一 slot。Tree 冲突检查只扫描请求所属 scope 的 HASH。/ 和 /local 使用 global HASH,但不会扫描 account 或 _system HASH。 跨 scope batch 会被拒绝。

不要混部仍使用旧单 HASH key 的版本和使用 scope key 的版本。先停止旧版本 写入,至少等待 2 * lock_expire_secs 让旧 key 过期,再启动新版本。

配置破坏性变更 ​

旧缓存配置不再兼容,升级前需要完成迁移:

已删除配置标准替代配置
storage.agfs.cache顶层 cache.provider + cache.params,并设置 storage.agfs.cachefs.backend="cache"
storage.agfs.queuefs.backend="redis" 和 queuefs.redisstorage.agfs.queuefs.backend="cache",并复用顶层 cache 配置
Redis mode="singleton"mode="standalone"
tls_enabled=trueendpoint 使用 rediss://
read_from_replica已删除,所有读命令统一访问主节点
Redis Provider key_prefixCacheFS 使用 cachefs.namespace;QueueFS 使用 queuefs.cache_key_prefix

OpenViking 会对已删除字段直接返回迁移错误,不再静默转换。

DynamicProvider ​

OpenViking 已内置 DynamicProvider 加载器和版本化 C ABI。默认 wheel 不携带第三方 SDK 或 Provider 动态库;需要接入外部缓存系统时,独立部署 Provider 动态库并配置 provider=dynamic。

动态库必须导出以下版本化入口:

text
openviking_cache_provider_v1

C 接口契约定义在 crates/ragfs/include/openviking_cache_provider_v1.h。Provider 返回的数据必须使用 Host allocator 分配,遵守文档中的内存所有权和关闭语义,禁止异常穿过 C ABI,并保证 handle 可以安全并发调用。

Provider 发布物应注明 ABI 版本、目标 OS/CPU、最低运行时版本、外部 SDK 版本、动态依赖和 SHA256。依赖外部原生库时,由 Provider 发布方通过 RPATH、LD_LIBRARY_PATH 或部署说明保证动态链接器能够找到依赖。

外部 Provider 可以独立升级,不需要重新构建默认 OpenViking wheel;只有 DynamicProvider ABI 不兼容时,才需要同步升级 OpenViking。

配置项 ​

顶层 cache 与 storage 并列:

参数类型默认值说明
providerstr无Provider 名称,支持 redis 和 dynamic
paramsobject{}Provider 自有参数

storage.agfs.cachefs 支持以下业务配置:

参数类型默认值说明
backendstr"local"local 沿用原逻辑;cache 启用 CachedFileSystem
namespacestr"openviking"缓存命名空间,用于隔离不同部署或租户
max_file_size_bytesint1048576允许进入缓存的最大完整文件大小
traversal_modestr"backend"递归 API 使用 backend 遍历或 cached_traversal
bypass_prefixeslist[str][]强制绕过缓存的路径前缀

storage.agfs.pathlock 控制 PathLock 存储:

参数类型默认值说明
providerstr"filesystem"filesystem、memory 或 cache
namespacestr 或 nullnullprovider=cache 时必填,同一部署的协作进程共享此命名空间
lock_expire_secsfloat30.0锁 stale 超时;不得小于 1.0

Redis 配置:

参数默认值说明
mode"standalone"Redis 部署模式
endpoints["redis://127.0.0.1:6379"]Redis 连接地址;redis:// 使用明文传输,rediss:// 使用 TLS
username""Redis ACL 用户名
password_env""存放 Redis 密码的环境变量名
pool_size32命令并发数
connect_timeout_ms1000连接超时
command_timeout_ms20命令超时
default_ttl_seconds3600默认 TTL;0 表示不设置 TTL
tls_insecure_skip_verifyfalse跳过 rediss:// 证书校验,仅用于受控测试环境

所有 Redis 读命令均发送到主节点,避免 QueueFS 读取到延迟的队列状态。需要传输加密时使用 Redis TLS,CacheRuntime 不额外增加应用层数据加密格式。

DynamicProvider 配置:

cache.params.library 由 OpenViking 用于加载动态库,其余字段由外部 Provider 定义并作为 JSON 传入 create。实际参数以 Provider 发布说明为准。

json
{
  "cache": {
    "provider": "dynamic",
    "params": {
      "library": "/opt/openviking/providers/libopenviking_cache_provider.so",
      "endpoint": "127.0.0.1:31501",
      "request_timeout_ms": 5000
    }
  }
}

整体架构 ​

RAGFS 将 Provider 访问和各业务消费者分开:

  • CachedFileSystem:实现文件系统语义,包括 cache hit/miss、backend 回源、回填、失效、generation 校验和指标。
  • CacheRuntime:向业务层提供统一基础操作,启动时绑定内置 RedisProvider 或外部 DynamicProvider。
  • QueueFS 和 RedisPathLockProvider:复用共享 CacheRuntime 存储队列和分布式锁。RedisPathLockProvider 要求使用内置 RedisProvider。

调用关系:

text
OpenViking
  -> RAGFS / MountableFS
       |-> CachedFileSystem ------\
       |-> QueueFS cache backend --+-> shared CacheRuntime -> RedisProvider
       `-> RedisPathLockProvider --/                     `-> DynamicProvider
       `-> Backend FileSystem

CachedFileSystem 和 QueueFS 可以使用 RedisProvider 或 DynamicProvider。 RedisPathLockProvider 依赖 Redis Lua,因此只使用 RedisProvider。文件系统语义 保留在 CachedFileSystem;外部 Provider 只通过稳定 C ABI 提供 key-value 基础操作。

缓存对象 ​

RAGFS 主要缓存三类对象。

文件缓存 ​

文件 key 使用稳定命名空间和路径 hash:

text
ragfs:v1:{namespace}:file:{hash(path)}

文件 value 是 CacheEnvelope,包含文件内容、对象类型、路径和 generation 快照。全量读取命中后,RAGFS 会先校验 envelope 和 generation,再返回内容。

默认策略会优先缓存 .abstract.md 和 .overview.md 这类摘要文件;超过 max_file_size_bytes 的文件不会进入缓存。非全量 range read 也会绕过缓存。

目录缓存 ​

目录 key:

text
ragfs:v1:{namespace}:dir:{hash(path)}

目录缓存保存 backend 原始 read_dir entries,而不是权限过滤后的最终结果。权限、角色和 agent context 仍在 OpenViking 上层实时处理。

这样同一份目录缓存可以服务 ls、tree、glob、grep 的文件收集阶段,以及删除或移动前的路径收集。

子树 Generation ​

子树 generation key:

text
ragfs:v1:{namespace}:subtree:{hash(scope)}

remove_all 和目录 rename 可能让 Provider 中残留子孙 key。RAGFS 通过 bump subtree generation,让旧 envelope 的 generation 快照失效,后续真实读取会回源并重建缓存。

一致性与失效 ​

单写者场景下,RAGFS 不需要分布式写锁。关键是按文件系统语义维护三类失效:

  • 文件变更:删除或更新 file_key(path),删除 dir_key(parent)。
  • 目录变更:删除目录自身和父目录的 dir_key。
  • 子树变更:对递归删除和目录 rename bump subtree generation。

典型写入顺序:

text
获取进程内操作锁
-> 执行 backend 变更
-> 更新或删除相关 cache key
-> 必要时 bump subtree generation
-> 返回结果

如果 Provider 失败,RAGFS 会以 backend 为准,并让受影响路径进入短期 bypass,避免继续读取可能陈旧的缓存。

请求合并 ​

当多个请求同时读取同一个未缓存的小文件或目录时,CachedFileSystem 会用进程内 inflight 表合并请求:

text
第一个 miss 请求成为 leader,负责回源和回填。
后续相同 key 的请求成为 follower,等待 leader 结果。
请求完成后删除 inflight 条目。

这只减少同一 OpenViking 进程内的重复 backend 访问,不改变 Provider 的一致性边界。

缓存策略 ​

RAGFS 会自动绕过不适合缓存的路径:

  • 锁文件:.path.ovlock、*.lock、*.lck
  • 控制文件:enqueue、dequeue、peek、ack
  • 瞬时状态:heartbeat、lease、cursor、offset、pid
  • 用户通过 bypass_prefixes 指定的路径前缀

权限敏感目录建议加入 bypass_prefixes。如果目录原始 entries 本身就依赖调用者权限,不应缓存该目录。

故障与观测 ​

缓存层不能影响文件系统正确性:

  • get 失败:回源 backend。
  • put 失败:记录错误,路径进入 bypass。
  • delete 失败:记录错误,路径或 scope 进入 bypass。
  • Provider 不可用:不返回旧缓存,以 backend 结果为准。

建议重点观察:

  • cache hit / miss / bypass
  • stale generation
  • provider get / put / delete 延迟
  • cache set / delete 失败
  • inflight leader / follower / backend saved
  • backend fallback 字节数

推荐使用顺序 ​

  1. 关闭缓存验证 backend 基线行为。
  2. 用内置 redis 验证真实远程缓存收益。
  3. 对高性能或闭源缓存系统使用独立发布的 DynamicProvider .so。
  4. 先缓存摘要文件和 raw read_dir,再扩展到更多普通小文件。
  5. 将锁、控制面和权限敏感路径加入 bypass_prefixes。

RAGFS 缓存负责按文件系统语义失效,Provider 负责决定缓存对象放在哪里。backend 仍是事实来源,缓存命中必须先通过 envelope 和 generation 校验才会返回。

启用后,将重复读取和文件更新后的失效行为与基线比较。确认返回内容保持最新,再判断命中率提升是否带来收益。

Open source under the AGPL-3.0 License. Font licenses