向量写进库里只是开始:RAG 向量索引与 Collection 管理实践

6 阅读10分钟

Milvus、索引算法选择、度量类型、逻辑/物理 Collection 分层、BM25 稀疏向量、推荐引擎、统计与日志。我想解决的不是“把向量塞进库里”,而是“怎么把向量资产组织成真正可检索、可管理、可演进的基础设施”。

上一篇我写了向量化模块,核心观点是:
Embedding 不是一次 API 调用,而是在生产后续检索系统依赖的向量资产。

那有了向量资产以后,接下来的问题就是:

这些向量应该怎么存,怎么组织,怎么建索引,怎么被后续模块稳定消费。

很多教程到这里会一句话带过:

  • 连接向量库
  • 创建 collection
  • 插入向量
  • 完成

这个流程对 demo 当然够了。
但一旦你真的想把系统做成工程项目,很快就会发现,索引层其实远远不只是“写进库里”:

  • Collection 应该怎么划分
  • Index 和 Collection 是不是一个概念
  • 算法应该怎么选
  • 度量类型应该怎么定
  • 维度不同的模型能不能混放
  • 稀疏向量怎么和稠密向量共存
  • 索引怎么恢复、怎么统计、怎么记录操作历史

所以在 RAG Pipeline Hub 里,我把向量索引单独做成了一个模块。
这篇文章,我就专门聊聊这部分设计。

项目地址:

  • GitHub: https://github.com/qingni/rag-pipeline-hub

为什么索引层不是简单的向量入库

很多人刚接触 RAG 时,会把索引层理解成一个纯存储问题:

拿到向量 -> 写进数据库 -> 搜索

但从工程角度看,这个理解太简化了。

因为索引层真正负责的,其实是下面这些事:

  • 管理向量的物理存储结构
  • 决定搜索性能和近似精度的平衡
  • 组织不同知识库、不同模型、不同维度的数据
  • 为后续混合检索预留稀疏表示能力
  • 记录索引状态、统计、历史和操作行为

也就是说,它是整个 RAG 系统的检索基础设施层

如果这层设计混乱,后面检索模块再聪明,也会被基础设施限制住。

我为什么把索引模块从“搜索模块”里拆出来

这个项目里,向量索引模块和搜索查询模块是拆开的。

这是一个我比较坚持的设计。

原因很简单:

  • 索引模块关心的是“怎么建、怎么存、怎么管”
  • 搜索模块关心的是“怎么查、怎么排、怎么提质量”

这两者强相关,但职责并不一样。

我更愿意把它们理解成:

  • 向量索引模块:Write / Manage
  • 搜索查询模块:Read / Retrieve

这种拆法的好处是很明显的:

  • 索引逻辑不会和搜索逻辑搅在一起
  • 方便单独管理 Collection、Index、统计和推荐
  • 后面要替换检索策略时,不需要连索引管理一起改
  • 整个系统的职责边界更清晰

这也是为什么我不想把“建索引”和“做搜索”全堆在一个 service 里。

这个模块在整条链路里负责什么

向量索引模块位于向量化之后、搜索查询之前。

它负责的事情可以概括成一句话:

把向量化结果转换成真正可搜索的检索基础设施。

从流程上看,大致是这样:

选择向量化结果
  -> 推荐索引配置
  -> 选择算法和度量类型
  -> 创建 / 复用 Collection
  -> 写入稠密向量和稀疏向量
  -> 建立索引
  -> 保存元数据、统计和操作日志

所以这个模块的输出,不只是“Milvus 里多了一批数据”,而是:

  • 可管理的 Index 记录
  • 可复用的 Collection
  • 可追踪的统计与日志
  • 可供搜索模块读取的检索结构

为什么 CollectionIndex 不应该混为一谈

这是很多项目最容易写得比较糊的地方。

在不少 demo 里,Collection 和 Index 几乎是一个意思。
但当数据规模、模型数量、知识库数量上来之后,你会发现这两个概念分不开。

我理解中的区别大概是:

Collection

更偏“数据容器”和组织边界。

它回答的问题是:

  • 这些向量属于哪个知识库
  • 它们以什么维度存
  • 共享哪些字段结构
  • 会不会和别的向量资产混放

Index

更偏“搜索结构”和性能策略。

它回答的问题是:

  • 用什么算法查
  • 用什么度量方式算相似度
  • 追求精度还是速度
  • 当前状态是不是已经可用

换句话说:

Collection 更像容器,Index 更像搜索配置。

只有把这两个概念拆开,你后面做管理、推荐、统计和搜索联动时,系统才不会越来越乱。

为什么我做了逻辑 Collection / 物理 Collection 分层

这部分是索引模块里我比较看重的一个设计点。

因为在真实场景里,你很容易遇到一个问题:

  • 不同文档可能来自同一个逻辑知识库
  • 但它们的 Embedding 模型不一定相同
  • 向量维度也不一定相同

如果把这些向量硬塞进同一个物理 Collection,问题就会非常多:

  • 维度不一致无法共存
  • 管理边界混乱
  • 后面多模型扩展很难做

所以这个项目参考了更工程化的做法,把 Collection 分成两层:

逻辑 Collection

面向业务概念,比如某个知识库、某类文档集合。

物理 Collection

面向真正的存储结构,通常按维度等底层条件拆分。

这件事看起来有点“多做了一层抽象”,但它的价值很实在:

  • 支持多模型共存
  • 让不同维度的向量有清晰归属
  • 为跨 Collection 联合检索打基础
  • 让后续迁移和扩展更容易

我越来越觉得,很多工程设计在小规模时看起来像“过度设计”,但等你真的进入多模型、多知识库场景时,它们会变成必须项。

为什么要在索引层就预留 BM25 稀疏向量能力

这个项目后面的搜索模块支持混合检索,也就是:

  • 稠密向量召回
  • 稀疏向量召回
  • 融合
  • 精排

如果只看搜索层,好像稀疏向量是搜索时才需要考虑的。
但实际上,稀疏能力应该从索引层就开始准备。

因为搜索层要做混合检索,前提是索引层已经准备好了:

  • 稠密向量字段
  • 稀疏向量字段
  • 对应的统计信息
  • 能支撑 BM25 查询的基础结构

在这个项目里,我用 jieba 做 BM25 稀疏向量生成,并把这部分能力放进索引模块中一起落地。

这么做的意义是:

混合检索不是搜索阶段临时拼出来的,而是从索引基础设施阶段就已经开始布局。

这会让后面的搜索模块更干净,也更容易解释。

为什么算法选择和度量类型不该靠用户盲猜

索引层另一个很常见的问题是:

系统虽然支持很多算法,但用户其实并不知道怎么选。

比如常见的索引算法就包括:

  • FLAT
  • HNSW
  • IVF_FLAT
  • IVF_SQ8
  • IVF_PQ

如果只把这些选项暴露出来,却不给任何建议,用户往往只能凭感觉点一个。

但这些算法背后对应的是非常不同的取舍:

  • 小数据量更适合精确搜索
  • 中等规模更适合平衡精度和速度
  • 大规模更需要近似搜索和更强压缩

同样,度量类型也不是随便填:

  • COSINE
  • L2
  • IP

这些选择会和 Embedding 模型的特性强相关。

所以在这个项目里,我专门做了推荐引擎。
推荐逻辑会结合:

  • 数据量
  • 向量维度
  • 模型类型
  • 常见经验规则

来推荐:

  • 索引算法
  • 度量类型
  • 相关参数

这件事的核心价值不是“系统更自动化”,而是:

让用户在合理默认值上开始,而不是在完全未知的配置空间里乱试。

为什么统计、日志和审计记录非常重要

索引层看起来不像用户直接操作最多的页面,但它其实很需要可观测性。

因为一旦这里出问题,影响会非常大:

  • 写入失败
  • 状态不一致
  • 搜索结果异常
  • Collection 匹配错误
  • 重复创建索引

所以在这个模块里,我专门保留了:

  • 索引元数据
  • 操作日志
  • 统计信息
  • 推荐采纳记录

这些能力平时看起来不显眼,但在工程实践里非常关键。

比如你需要回答这些问题时,就会发现它们的重要性:

  • 某个索引是什么时候建的
  • 用了什么模型和什么参数
  • 为什么这次推荐用户没采纳
  • 当前状态是不是 READY
  • 某次恢复和持久化有没有成功

我一直觉得,工程化系统不是“功能多”,而是:

关键节点出现问题时,你能不能知道它为什么出问题。

这一层和“把向量写进 Milvus”到底差在哪

如果只从代码量看,最小版本的向量索引其实很短:

连接 Milvus
  -> 创建 collection
  -> 插入向量
  -> 建 index

但在这个项目里,我更关心的是下面这些更工程化的能力:

  • 逻辑 / 物理 Collection 分层
  • 稠密 + 稀疏双路向量准备
  • 索引配置推荐
  • Collection / Index 独立管理
  • 状态记录
  • 统计与审计日志
  • 恢复和持久化能力

所以它和普通 demo 的差别,不在“能不能建索引”,而在:

能不能把索引层做成一个长期可维护的检索基础设施。

我对这个模块的一个核心判断

如果只让我总结一句话,我会说:

向量写进库里只是开始,真正困难的是把这些向量组织成一套支持多模型、多知识库、混合检索和长期演进的基础设施。

这一步做得好,搜索模块才有稳定地基。
这一步做不好,后面所有检索优化都会不断撞上基础设施的边界。

所以在 RAG Pipeline Hub 里,我才会把索引模块单独拆出来,而不是把它当成搜索前的一段临时代码。

下一篇写什么

索引模块解决的是“怎么存、怎么建、怎么管”。
接下来真正面向结果质量的问题就是:

怎么把 Dense Recall、Sparse Recall、RRF、Reranker 和 Query Enhancement 串成一条真正可用的检索链路。

所以下一篇我会继续写:

《RAG 检索不能只靠向量 TopK:Dense + Sparse + RRF + Reranker 的完整链路》

项目地址:

  • GitHub: https://github.com/qingni/rag-pipeline-hub

如果这篇文章对你有帮助,欢迎:

  • 点个 star
  • 提个 issue
  • 留言说说你在索引层最常踩的坑

如果你也做过 Milvus、Collection 管理、索引算法调优或者混合检索底座设计,欢迎交流你的经验。
我越来越觉得,很多 RAG 系统看起来差在“搜索效果”,其实根因往往早在索引层就埋下了。