搭 RAG 知识库的团队经常在选型时纠结:向量数据库要不要选带对象存储的?原文要不要塞进向量库?这些纠结的背后其实是同一个问题没拆开——RAG 的数据有三种形态,生命周期和访问模式各不相同,放在同一层里管,检索层会被无关的存储压力拖垮。把三种形态拆开,各归各的层,选型自然就清楚了。
三种形态,三种访问模式
原始文档(PDF、HTML、Office 文件)体积最大,写入后几乎不变,访问模式是"偶尔整读一份",通常是解析管线和审计取证在读。切块产物(解析后的分段文本和元数据)是中间产物,随解析管线批量重生成,读它的只有入库程序。向量(embedding 之后的数值数组)体积小、要求毫秒级随机读,被检索服务高频访问。
三种访问模式对应三种存储需求:整读、批量吞吐、低延迟随机读。前两种是对象存储的主场,第三种是向量数据库的主场。给个体感数字:一块 1024 维、float32 存储的向量是 4 KB,一百万个切块的全部向量也就几个 GB,向量库的本机盘放得下。真正占空间的是原文,几 TB 的 PDF 和网页库在对象存储这边,向量库只管索引和向量。两层各自的容量曲线一个按 TB 走、一个几乎不动,监控上也不会互相干扰。
原文和切块:对象存储这一层
原文进对象存储没什么争议,值得说清的是放在 RustFS 这类 S3 存储上能白拿多少管理能力。版本化让解析管线的每次重跑可回溯:新解析结果写成新版本,解析出问题回滚版本即可。生命周期规则可以把 90 天未访问的中间切块产物自动降级,省下的空间比想象的多:切块产物通常是原文体积的两三倍。presigned URL 让检索结果引用原文时不用暴露存储凭据,前端拿到的链接带过期时间,权限控制在服务端。
切块产物建议和原文分桶放。切块是要重生成的数据,桶上配不同的生命周期;原文桶上开版本化和对象锁,切块桶不用。混在一个桶里,这两套策略会互相打架。落下来就是三条命令的事:
mcaliassetrfs http://rustfs:9000$AK$SKmcmb rfs/raw-docs rfs/parsed-chunksmcversionenablerfs/raw-docs原文桶保版本,切块桶不保,前面说的策略就落在最后一行的差别上。presigned URL 的过期时间也顺手定个规矩:给前端签 URL,有效期按文档大小给,几 MB 的文件五分钟足够,别图省事签一天。链接有效期越长,被转发出去造成越权读取的窗口就越大,这个参数值得写进检索服务的统一配置,而不是散在各处。对象锁跟 presigned 这条读链路不冲突:对象锁拦的是删除和覆盖,锁着的对象拿预签名链接照常能读,别指望用锁去收紧读的口子;读的边界靠桶策略加 presigned 的有效期来管,锁只管写删那头。
对象存储这一层不做检索。判断依据很朴素:检索需要的是"给一个语义,毫秒级返回最近的 N 条",对象接口给的是"给一个 key,返回整个对象"。语义检索的活归向量库,别让检索服务去扫对象存储。还有一条容易被忽略的边界:生命周期规则把切块降级、甚至清掉之后,检索结果里的 presigned URL 指向的是原文,引用照样有效。切块的存亡不影响引用,这正是"原文放对象存储、向量放检索层"带来的容错空间。但切块桶也不是切块的唯一副本:向量库入库时通常把切块文本连着向量一起存了,生命周期清掉桶里那份,向量库里的还在,两份中间产物从此不同步。哪天换切块策略重灌,对账范围要把向量库里那份也算进去,别只盯着切块桶的桶清单。
向量:检索层的事,但持久层可以想清楚
向量数据库负责 ANN 索引和毫秒级查询,这一层没有替代品。要拆开看的是它的持久层:索引文件和原始数据总要落在某块盘上。向量库自身管的是这些文件的编排,底下的容量、备份、多副本,对象存储接得住的部分就交给对象存储。分层边界在于:检索延迟由向量库负责,数据不丢由对象存储负责,两个承诺别指望同一个组件给。不少向量库支持把段文件或快照直接落到 S3,配一个 endpoint 就接上了。接上之后这层关系更干净:向量库管索引和查询,段文件的容量、备份、多副本由对象存储兜底,向量库重装不丢数据。要分清的是,落到 S3 的是持久层,运行时的查询仍然是向量库把索引加载进本机内存和盘上跑的,别把"支持 S3"理解成拿 S3 当在线查询存储;检索延迟按本地盘的账规划,S3 只负责数据不丢和可重建。
embedding 模型换版本是 RAG 系统躲不掉的事。换模型意味着全量重算向量,这时候原文在对象存储上的价值就显出来了:切块和向量都可以删掉重生成,原文是不动的底座。这也是原文桶开版本化、切块桶不开的另一个理由:重生成型的数据不值得保版本。原文桶保版本同样有账要算:覆盖和删除都会留旧版本,容量只增不减,官方文档把这条写在版本化页的运维注意事项里;配套动作是给非当前版本配生命周期清理,文档还提醒了顺序,先定恢复期和保留期,再配非当前版本的清理规则,别让清理跑在恢复需求前面。
按生命周期核对一遍
分层放完,用数据生命周期过一遍每层的能力是否对得上:
- 入库阶段:解析管线读原文桶、写切块桶,S3 接口批量吞吐,multipart 上传用上。
- 检索阶段:向量库毫秒级响应,命中后用 presigned URL 引用原文,全程不碰存储凭据。
- 重建阶段:换模型或换切块策略,删切块桶重灌,原文桶不受影响。
- 审计阶段:谁在什么时候入库了哪份文档,对象锁和版本化给取证留底。
四条都对得上,这套分层就站住了。任何一条对不上(比如检索服务要扫对象存储找数据,或者重建要动原文桶),说明边界画错了位置。
RustFS 在这套分层里的角色是下面两层(原文和切块)的承载者,给的是 S3 接口加版本化、对象锁、生命周期、presigned 这组管理能力;向量检索不是它的活,也不该是。分层清晰之后,每一层的选型都可以独立替换:向量库换型不动原文,解析管线重写不动检索,这是拆层真正的回报。
分层里点到的版本化、对象锁、presigned URL,官方文档各有专页可查:版本化、对象锁;RustFS 1.0.0 在 2026 年 9 月 16 日发布,源码在 GitHub 的 rustfs/rustfs 仓库。搭第一版环境时,原文桶和切块桶的桶策略直接照文档样例抄,比从零琢磨快。