RAG 检索层选型:Fumadocs、Qdrant、Elasticsearch、Milvus、pgvector 怎么选
系统拆解自建 RAG 服务的检索层选型逻辑,解释文档站搜索、语义检索、向量数据库和全文检索的边界,并说明中小型知识库系统为什么采用 Fumadocs / Orama + Qdrant 的组合。
RAG 检索层选型:Fumadocs、Qdrant、Elasticsearch、Milvus、pgvector 怎么选
RAG 检索层选型讨论的不是“哪一个数据库最强”,而是系统中到底存在几种不同的检索问题。
一个知识库产品通常同时面对两类用户行为。第一类是人在文档站里找文章:他记得某个标题、某个术语、某个标签,希望快速跳到对应页面。第二类是模型在回答前找上下文:它需要从切片后的知识片段中召回语义相关内容,并且这些内容必须能被权限过滤、引用展示和日志追踪。
这两件事都叫“检索”,但工程目标并不相同。如果把它们混在一起,选型就会变成工具名比较:Fumadocs 能不能搜、Qdrant 能不能搜、Elasticsearch 能不能搜、Milvus 是否更企业级、pgvector 是否更简单。这样的讨论很快会失焦,因为工具的能力重叠不代表职责应该重叠。
以 Way To Agent 当前版本为例,检索层的职责划分很明确:
Fumadocs / Orama:负责公开文档站搜索
Qdrant:负责登录后 RAG 语义检索
PostgreSQL:负责业务数据、文档元数据、版本、Chunk 元数据、日志和额度
Redis:负责任务队列、缓存和限流
MinIO:负责上传文件、简历、导出报告和附件
Elasticsearch:不作为默认依赖,仅作为后续高级全文检索的可选方案
Milvus:大规模向量检索备选方案
pgvector:小型简化方案备选方案
这不是为了少用技术而少用技术,而是为了让每个组件只承担它最该承担的责任。
两种检索不是同一个问题
公开文档搜索的用户是读者。读者通常不是在问一个开放问题,而是在找一篇文章、一个章节或一个概念入口。
典型输入是:
RAG
LangGraph
Qdrant
流式输出
Token 成本
系统要返回的是文章列表、标题、摘要、命中片段和跳转入口。它需要响应快,不需要登录,不应该产生成本,也不应该依赖后端 AI 服务。对于一个静态知识库网站来说,Fumadocs / Orama 正好适合这类需求。
RAG 语义检索的用户表面上是人,实际调用者是模型链路。用户提出的是自然语言问题,系统需要先把问题转成向量,再从文档切片中找到相关上下文。
典型链路是:
用户问题
-> 问题向量化
-> Chunk 向量召回
-> 文档状态和权限过滤
-> 可选 Rerank
-> Prompt 上下文拼接
-> LLM 生成答案
-> 返回引用来源
-> 记录调用日志和额度
它关心的不是“哪篇文章标题命中”,而是“哪些片段足以支撑模型回答”。片段必须有 chunk_id、source_url、heading_path、文档版本、可见性和状态字段,否则回答无法追溯,也无法审计。
因此,公开文档搜索和 RAG 语义检索可以来自同一批 Markdown / MDX 内容,但不应该被粗暴地合并成同一个检索问题。
文档站内置搜索适合什么
Fumadocs / Orama 这类文档站搜索,价值在于贴近内容站体验。它知道页面、标题、目录和正文,它能跟随前端构建产物一起工作,不需要额外部署一个搜索服务。
它适合:
公开文档站
技术教程
API 文档
知识库文章入口
静态内容搜索
不登录可用的基础搜索
它的优势很直接:
接入成本低
部署链路短
与文档站导航一致
不产生模型调用成本
适合 SEO 和公开阅读
它不适合承担 RAG 语义检索:
不负责 Embedding
不负责 Chunk 级向量召回
不负责复杂权限过滤
不负责 Rerank
不负责引用溯源和 AI 调用日志
所以,在 Way To Agent 中,公开内容阅读、分类、标签、文章详情和基础搜索必须能静态独立运行。这个要求天然指向 Fumadocs / Orama,而不是先引入后端搜索服务。
专用向量数据库适合什么
Qdrant、Milvus、Weaviate、Pinecone、Chroma、LanceDB 都属于向量检索体系。它们的核心职责是保存向量,并按相似度找出最接近的点。
在 RAG 中,向量数据库保存的不是完整文章,而是切片后的 Chunk 向量及其 payload。一个 Qdrant Point 可以长这样:
{
"doc_id": "123",
"doc_version_id": "456",
"chunk_id": "04-rag-001-03",
"title": "RAG 是什么",
"heading_path": "RAG 知识库工程 > 核心概念",
"category": "RAG 知识库工程",
"tags": ["RAG", "Embedding", "检索"],
"source_url": "/docs/04-rag/001-what-is-rag",
"visibility": "public",
"is_active": true
}
这个 payload 不是装饰字段,它决定了检索结果能否进入生产系统。没有 doc_version_id,就很难处理文档更新后的索引一致性;没有 source_url 和 heading_path,就无法展示引用;没有 visibility 和 is_active,就无法过滤权限和失效内容。
专用向量库的优势在于边界清晰:
向量召回能力专注
Payload / Metadata 过滤模型清楚
适合保存 Chunk 级语义索引
便于后续替换 Embedding 模型或重建索引
更贴近 RAG 上下文召回的工程模型
它的代价也必须承认:
需要额外部署和备份
需要处理向量索引重建
需要和业务数据库保持引用关系
不能天然替代文档站搜索体验
因此,Qdrant 在这里不是“更高级的搜索框”,而是 RAG 在线问答链路中的语义召回组件。
为什么不默认使用 Elasticsearch
Elasticsearch 很强,尤其适合全文检索、倒排索引、BM25、中文分词、复杂过滤、搜索高亮、搜索分析和后台搜索运营。
如果一个系统本身就是企业搜索平台,或者已经有大量文档、复杂字段、跨业务搜索和搜索运营需求,Elasticsearch 很可能是合理的默认选型。
但 Way To Agent 当前版本的约束不同:
前端文档站使用 Next.js + Fumadocs + MDX
公开文档搜索可以由 Fumadocs / Orama 承担
RAG 问答需要的是 Chunk 级语义召回
目标支持家庭服务器部署
当前版本要减少非必要运行依赖
在这些条件下,默认引入 Elasticsearch 会带来额外复杂度:
双索引写入
双索引一致性
更多内存占用
更多部署、备份和恢复工作
更多故障排查路径
开发期需要维护更多环境变量和服务健康检查
这里并不是否定 Elasticsearch,而是把它放到正确的位置:当 Fumadocs / Orama 的前端搜索索引变得过大,当需要复杂中文分词、同义词、搜索高亮、后台统一搜索、搜索分析和搜索运营时,再引入 Elasticsearch 更合理。
当前不默认引入 Elasticsearch,是一种架构克制。
为什么选择 Qdrant
Qdrant 适合中小型自建 RAG 系统。它部署相对轻,API 清晰,payload filter 容易理解,和 Chunk 级语义检索模型贴合。
当前系统的约束是:
中小规模知识库
自部署和家庭服务器友好
RAG 是登录后的增强能力
需要清晰展示向量检索工程能力
暂不需要分布式向量检索集群
在这个约束下,Qdrant 的优势比“是否最强”更重要:
比 Milvus 更轻,运维门槛更低
比 pgvector 更能体现专用向量检索边界
比把向量检索塞进业务库更利于后续扩展
足以支撑当前版本的 RAG 问答和引用溯源
Qdrant 的默认职责应该很窄:
保存 Chunk 向量
保存用于过滤和引用的 payload
按问题向量召回候选 Chunk
按文档状态、可见性、分类、标签过滤
返回 chunk_id 供业务库查询元数据和日志记录
业务主数据仍然由 PostgreSQL 管理。不要把 Qdrant 当成文档库,也不要把业务权限规则散落到 Qdrant 中。向量库返回候选,业务系统负责最终治理。
为什么不是 Milvus
Milvus 是非常成熟的向量数据库,尤其适合大规模、分布式和高吞吐场景。
它更适合:
千万级、亿级向量
分布式扩展
Kubernetes 部署
企业级高可用
独立检索团队维护
多业务共享向量检索平台
Way To Agent 当前版本不是这个量级。如果为了“看起来企业级”直接上 Milvus,架构评审时会遇到一组很现实的问题:
当前向量规模有多大?
为什么需要分布式?
家庭服务器怎么运维?
索引重建和备份怎么做?
谁负责集群监控?
如果这些问题没有明确答案,那么 Milvus 在当前版本就是过度设计。
更稳妥的表达是:Milvus 是未来大规模向量检索备选方案。当前选择 Qdrant,是因为数据规模、部署环境、开发成本和教学目标更匹配。等到向量规模、并发压力、部署形态或团队运维能力真的发生变化,再讨论替换才有意义。
为什么不是 pgvector
pgvector 的优势是组件少。对于小型内部工具,它可能是非常好的方案:业务数据在 PostgreSQL,向量也在 PostgreSQL,部署和备份都简单。
但它也会带来边界耦合。RAG 一旦成为核心能力,业务库既承担事务主数据,又承担向量检索,性能、索引策略、重建流程和权限过滤都会更难拆分。
Way To Agent 的目标不仅是做一个能运行的问答功能,还要把 RAG 工程链路讲清楚。使用专用向量库,有利于学习者理解:
Embedding 模型与向量维度
Collection / Point / Payload
向量召回与 payload filter
索引重建和版本切换
Chunk 元数据与引用溯源
业务库和检索库的职责分离
所以 pgvector 不是低级方案,而是“小型简化方案”。当系统目标是减少组件、快速交付内部工具时,它可以优先考虑;当系统目标是展示完整 RAG 工程能力并保留扩展空间时,Qdrant 更合适。
最终架构如何落地
最终架构不是一张组件清单,而是两条链路。
第一条是公开文档站链路:
Markdown / MDX 内容目录
-> Next.js + Fumadocs 构建文档站
-> Fumadocs / Orama 生成本地搜索索引
-> 匿名用户搜索、阅读、跳转文章
这条链路不调用模型,不依赖 arbor-api,不消耗 AI 额度。
第二条是 RAG 语义检索链路:
Markdown / MDX 内容目录
-> 业务 API 同步文档元数据和版本
-> Indexer Worker 解析 Markdown / MDX
-> 标题感知切片
-> PostgreSQL 保存 Chunk 元数据
-> Embedding API 生成向量
-> Qdrant 保存向量和 payload
在线问答时:
登录用户提问
-> arbor-api 校验登录和额度
-> arbor-ai-service 向量化问题
-> Qdrant 召回候选 Chunk
-> 可选 Rerank
-> 组装 RAG Prompt
-> 外部 LLM 生成答案
-> 返回答案和引用来源
-> arbor-api 记录日志和扣减额度
这两条链路共用内容源,但运行时边界不同。一个面向读者找文章,一个面向模型找上下文。
代码层应保留抽象
即使当前默认使用 Qdrant,也不应该把 Qdrant 细节散落到所有业务代码中。
更合理的方式是保留向量存储抽象:
VectorStoreProvider
├── QdrantVectorStoreProvider,默认实现
├── ElasticsearchVectorStoreProvider,可选增强
└── MilvusVectorStoreProvider,未来备选
接口至少应覆盖:
upsert_chunks:写入或更新 Chunk 向量
search:按问题向量和过滤条件召回 Chunk
delete_by_doc_version:文档版本失效时删除旧索引
health_check:用于服务健康检查
这样做不是为了提前写很多适配器,而是为了避免业务代码直接依赖某个具体向量库的 SDK 结构。抽象保留得恰当,未来扩展才不需要重写整条 RAG 链路。
最容易选偏的地方
做 RAG 就必须用向量数据库
不完全对。小型系统可以使用 pgvector,已有搜索基础设施的企业也可能先用 Elasticsearch / OpenSearch。关键不在“有没有向量数据库”,而在系统能否稳定完成检索、引用、权限、成本和评估闭环。
Fumadocs 搜索可以替代 RAG 检索
不对。Fumadocs 搜索解决的是读者查文章,RAG 检索解决的是模型回答前找上下文。两者面对的输入、输出和质量指标都不同。
Elasticsearch 能做向量检索,所以 Qdrant 没意义
不对。Elasticsearch 的确可以做向量检索,但是否要把它作为默认 RAG 检索组件,要看系统是否已经需要复杂全文搜索。Way To Agent 当前版本没有这个必要。
Milvus 更强,所以应该一步到位
不对。强不等于适合。技术选型必须和数据规模、部署环境、团队能力和运维成本匹配。
最后结论
中小型知识库的公开文档站可以使用 Fumadocs + MDX 构建,基础搜索由 Fumadocs / Orama 承担,所以普通文章搜索不需要单独部署 Elasticsearch。登录后的 AI 问答属于 RAG 场景,需要对文档 Chunk 做 Embedding、语义召回、权限过滤和引用溯源,因此使用 Qdrant 作为默认向量检索组件更合适。Elasticsearch、Milvus、pgvector 都不是被排除,而是按场景放在后续增强或替代位置。
这个结论的重点不是 Qdrant 这个名字,而是边界划分:
公开文档体验和 AI 检索能力分层
免费能力和登录限量能力分层
静态内容访问和动态模型调用分层
当前运行依赖和未来扩展方案分层
能说清楚这些边界,比背出某个检索引擎的功能列表更重要。
落地建议
1. 画出公开文档搜索链路和 RAG 语义检索链路,标出它们共用的内容源和不同的运行组件。
2. 为 Qdrant Point 设计 payload 字段,至少包含 doc_id、doc_version_id、chunk_id、heading_path、source_url、visibility、is_active。
3. 设计 VectorStoreProvider 接口,包含 upsert_chunks、search、delete_by_doc_version、health_check。
4. 写出未来引入 Elasticsearch 时需要新增的索引流程、同步任务和部署服务。
5. 解释为什么当前不默认使用 Elasticsearch / Milvus,以及什么条件下应该重新评估。
继续阅读
04-rag/002-rag-architecture.md
04-rag/005-embedding.md
04-rag/006-vector-database.md
04-rag/008-rerank.md
05-retrieval/002-local-doc-search.md
05-retrieval/003-qdrant-basic.md