摘要

知识库答错,绝大多数时候不是模型不行,而是链路某一段把事实弄丢了。把链路拆成 解析、分块、向量与索引、精排、生成 五个责任位,再记一句判据——“没存进去 / 没搜到 / 搜到了没排进 top-k / 进窗口了模型仍答错”,四类失败的修法完全不同——排查效率会比盯着 prompt 高一个数量级。

本文按这条链路走一遍:六个向量库各自在什么真实场景下好用、表结构为什么必须是三张、向量模型什么时候必须重建、RRF 为什么不是重排序、以及 ima 在千万级知识库、近千亿数据下怎么把一套 ES 用穿。文末给 24 条分级坑清单和一套最小评测方案。

一句话立场:知识库不是”上传文件 + 接大模型”,它是一条从解析到引用的工程链路,每一段都有独立的失败模式。


1. 先分清”谁把事实弄丢了”

同样一句”文档里明明写了,它却说不知道”,可能是五个完全不同的原因:

责任位典型症状该看什么
① 解析表格变一坨、双栏读成乱序、扫描件数字错抽 20 份最烂的文档,直接看解析结果
② 分块条款被拦腰切断,答案只有半句chunk 边界与 section_path
③ 向量与索引编号/型号查不到、过滤后只剩 3 条metric 与 opclass、ef_search、是否后过滤
④ 精排正确片段排在第 12 位,进不了 top-5召回列表的原始名次
⑤ 生成资料对了但答错、引用对不上prompt 约束与引用绑定方式

所以检索日志至少要记五样:query 原文、改写后的 query、召回 id + 分数、精排后名次、各段耗时。没有这五样,“答错了”就永远是个玄学。

先排除一个反例:如果知识总量只有几十页、且很少更新,直接把全文塞进上下文,通常比搭一套向量检索更准、更便宜。RAG 的收益随规模和更新频率递增,不是随”想不想要 AI”递增。这句话能救掉一半的过度设计。


2. 向量选型:三条路线,六个库

2.1 三条路线,不是六个产品

路线代表本质何时对
关系库扩展pgvector、ParadeDB向量是”一种新列”,与业务同库同事务已有 PG、向量少于 500 万~1000 万、要 JOIN/权限/事务
专用向量库Qdrant、Milvus、Weaviate为 ANN 重写存储与索引过滤复杂、单库千万到亿级、要多租户/量化
搜索引擎底座Elasticsearch / OpenSearchBM25 原生,向量是新增字段已有搜索栈、本来就要拼音/高亮/模糊

关键认知:今天这三条路线功能高度重叠。决定成败的往往不是”谁更快”,而是三件更无聊的事——过滤怎么走、混合检索要不要自己拼、运维谁来做。

2.2 横向对比

维度pgvectorQdrantMilvusES / OpenSearch
单节点舒适区约 500 万–1000 万千万级亿级(集群)千万~亿(多分片)
元数据过滤强(SQL),但 ANN 后过滤极强(payload 预过滤)强(标量索引,需显式建)强(filter 可下推)
原生混合检索需 pg_search / pg_trgm 拼稀疏+稠密,需自己上 SPLADE2.5+ 原生 BM25 function原生 RRF retriever
多租户RLS / 分区表payload + 分片database / collection / partition / partition key_routing + 多索引
运维最低(复用 PG)低(单二进制)最高(etcd / 对象存储 / 消息队列)中(已有集群则低)
主要代价HNSW 吃内存、大表建索引慢自建要自己做 HA / 备份小团队容易过度工程索引膨胀、写入放大

另外两个放不进这张表:

ParadeDB(pg_search)Weaviate
单节点舒适区同 PG(百万~千万)千万级
原生混合检索BM25 + 向量一条 SQLhybrid(alpha 调权重)
多租户RLS / 分区tenant 字段
主要代价扩展与 PG 大版本强绑定schema 与权重治理成本

社区里反复出现、值得直接引用的三条判断:

  1. 百万级硬上 Milvus 是典型过度设计——组件翻倍,稳定性反而下降。
  2. pgvector 的坑不在”能不能用”,在”过滤 + 内存”:HNSW 图常驻内存,m 从 16 调到 8 索引体积近腰斩;ef_construction 只影响建索引质量,不影响查询期内存(很多人搞反了)。
  3. 过滤是 RAG 的高频刚需(先按租户/文档类型/时间收窄,再算相似度),谁把预过滤做好,谁在真实业务里更省心。

2.3 逐个拆:实战里它到底好在哪

同一套 RAG,换个数据形态、换个团队规模,最优解就变了。

① pgvector —— “我本来就跑着 Postgres”

  • 知识库只是业务库里的一张表。电商后台已有 orders / products / users,要的是”我买过的商品里,最像这张图的”——语义排序必须和业务条件一起 JOIN。跨库方案得两次查询再在应用层合并,分页与计数立刻变成噩梦。
  • 多租户 + 行级权限。开 RLS 之后,“查不到无权数据”是数据库保证的,不依赖每个查询都记得加 WHERE tenant_id。这是小团队最容易漏、后果最重的一环。
  • 100 万~500 万 chunk,团队只有一两个后端。备份、监控、迁移、只读副本、连接池全部复用现有 PG 生态,等于零新增运维面。

机制上成立的原因很朴素:向量只是”一种新列”,事务、外键、级联删除、逻辑备份、时间点恢复全都免费;换 embedding 时可以新旧列双写,评测通过再切。这些是专用向量库要额外付钱买的。

起步参数:halfvec(1024) + HNSW m=16, ef_construction=64;查询期 ef_search=100(必须 ≥ top-k);按 kb_id 分区或建 partial index;先灌数据再 CREATE INDEX CONCURRENTLY。

什么时候别选:过滤选择率极低(只查全库 1%,却要 top-50);单表将破千万且没有分区方案;需要原生 BM25 又装不了扩展。

② ParadeDB / pg_search —— “我要在 PG 里同时拿到 BM25 和向量”

  • 受够了”两路召回 + 应用层融合”的胶水代码:一条 SQL 里同时做全文匹配与向量排序,再用 RRF 融合,少一套 ES、少一份数据同步。
  • 中英混合的技术文档、合同库:需要真正的 BM25,而不是 pg_trgm 的字面模糊——ERROR 50231、GB/T 19001 这种串,trgm 给不了 IDF 权重。
  • 已经在 PG 上、且 PG 自己可控(自建或支持该扩展的云)。

什么时候别选:托管 RDS 不给装扩展(很多云只放白名单扩展,这一条直接出局);PG 大版本升级节奏跟不上扩展;写入极高频(BM25 索引有写放大)。

③ Qdrant —— “过滤是主要矛盾,不是附带条件”

  • 多租户 SaaS,每次查询带 3~5 个过滤条件:租户、文档类型、时间范围、权限组、价格区间。payload 预过滤让”先过滤再算相似度”成为索引内部行为。
  • 电商 / SKU / 图库检索:结构化条件与语义条件混在一起,且 p95 有硬要求。
  • 换 embedding 要零停机:新建 collection 灌数据 → snapshot → 切 alias。这一条在”模型半年一换”的团队里价值极高。

什么时候别选:没人愿意维护有状态服务(要自己做 HA、备份、升级);需要原生 BM25(得上 SPLADE 稀疏向量,多一层模型)。

④ Milvus —— “亿级、多模态、有平台团队”

  • 内容平台全量去重、相似内容召回:几亿条图文向量,按天增量,需要冷热分层(DiskANN)。
  • 图像 / 视频帧 / 商品图检索:高维、高 QPS,需要 GPU 索引或多副本扛读。
  • 多模态统一底座:dense + sparse(2.5+ 原生 BM25 function)、标量过滤、分组搜索一套搞定。

什么时候别选:单库百万级、没有 K8s 经验。要维护 etcd、对象存储、消息队列,运维复杂度会把性能优势全部吃掉。

⑤ Weaviate —— “一个对象要多个向量”

  • 同一份内容要多种表示:标题向量、正文向量、摘要向量,甚至配图向量(named vectors)。检索时按字段分别召回再加权,比”拼成一段再编码”准得多。
  • 想要开箱的 hybrid:alpha 一个参数调 BM25 与向量的配比,不用自己写融合。
  • 内容站 / 电商的多模态检索,从原型到中等规模生产,希望少写胶水。

什么时候别选:团队排斥 schema 治理(改字段要动 schema);追求极简运维(模块组合一多,配置面反而比 Qdrant 大)。

⑥ Elasticsearch / OpenSearch —— “我本来就有一套搜索集群”

  • 站内搜索已经在用 ES:加语义检索不该再引入第二套系统。这正是 ima 的选择(见 §7)。
  • 中文体验是硬指标:拼音(zhishi / zs 命中”知识”)、首字母、ngram 模糊、同义词、高亮、聚合。ES 的 analyzer 生态是这六家里最成熟的。
  • 企业搜索 / 内容平台:文档级安全(DLS)+ _routing 按租户路由,权限与检索在同一层。

什么时候别选:集群本来就紧张(向量会显著抬内存与磁盘);只做语义检索、完全不需要全文——专用向量库更省。也别把 ES 当”免费的向量库”:mapping 与分片治理一旦失控,排查成本远高于换库。

2.4 反查表:按处境选,而不是按跑分

你的处境首选备注
已有 PG,chunk 少于 500 万pgvector要 BM25 就加 pg_search
已有 ES / OpenSearch 集群ES dense_vector集群吃紧再外挂 Qdrant
多租户 SaaS、过滤条件多Qdrant或 ES + _routing
单库千万级、要零停机换模型Qdrantalias 切换
亿级、GPU、多模态Milvus或 ES 多集群路由
一对象多向量WeaviateMilvus 多向量字段
站内搜索 + 语义搜索一体ES / OpenSearch—
内部 OA、细粒度权限pgvector + RLS或直接买
个人项目 / 桌面 / 离线SQLite-vec、LanceDBChroma 仅限原型
一周上线、不想运维云托管(Pinecone 等)注意数据出域
只有几十份文档别建库直接长上下文

3. 向量表结构:真正会让人返工的地方

3.1 三张表,不要一张大宽表

kb_document  1 ── n  kb_chunk  1 ── 1  kb_chunk_vec
(源文件/版本)      (检索最小单位)      (向量 + 模型指纹)

把 embedding 和 content、meta 塞进同一行有两个后果:更新元数据会重写整行(向量列走 TOAST,页分裂与 WAL 都会难看);换模型时无法灰度,因为新旧向量只能抢同一列。

3.2 一份能直接用的 DDL

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE kb_document (
  id           bigserial PRIMARY KEY,
  kb_id        bigint NOT NULL,              -- 租户/知识库边界,所有查询第一过滤条件
  source_uri   text   NOT NULL,
  content_hash bytea  NOT NULL,              -- 原文指纹:去重 + 增量判断
  version      int    NOT NULL DEFAULT 1,
  effective_at timestamptz,                  -- 生效日,比 created_at 重要
  status       text   NOT NULL DEFAULT 'pending',
  acl_groups   text[] NOT NULL DEFAULT '{}',
  meta         jsonb  NOT NULL DEFAULT '{}',
  created_at   timestamptz NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX ON kb_document (kb_id, source_uri, version);

CREATE TABLE kb_chunk (
  id           bigserial PRIMARY KEY,
  doc_id       bigint NOT NULL REFERENCES kb_document(id) ON DELETE CASCADE,
  kb_id        bigint NOT NULL,              -- 冗余存一份,过滤不必 JOIN
  parent_id    bigint,                       -- 父子块:命中 child,展开 parent
  ordinal      int  NOT NULL,
  section_path text,                         -- "第三章 > 3.1 用户创建"
  page_from    int, page_to int,
  content      text NOT NULL,
  token_count  int  NOT NULL,
  is_deleted   boolean NOT NULL DEFAULT false,
  tsv          tsvector GENERATED ALWAYS AS (to_tsvector('simple', content)) STORED
);

CREATE TABLE kb_chunk_vec (
  chunk_id  bigint PRIMARY KEY REFERENCES kb_chunk(id) ON DELETE CASCADE,
  model_id  text     NOT NULL,               -- 'bge-m3@1024'
  embedding halfvec(1024) NOT NULL
);

CREATE INDEX ON kb_chunk_vec USING hnsw (embedding halfvec_cosine_ops)
  WITH (m = 16, ef_construction = 64);
CREATE INDEX ON kb_chunk USING gin (tsv);
CREATE INDEX ON kb_chunk (kb_id, is_deleted);

3.3 七个字段,缺一个就要返工

字段不加会怎样
kb_id多租户只能”检索后过滤”,既是性能坑也是泄露坑
content_hash每次全量重灌,无法做增量;重复文档反复入库
version + effective_at新旧制度同时在库,模型引用了已废止条款
section_path块脱离上下文,“管理员可以创建用户”不知道属于哪一章
parent_id长文档要么切太碎丢上下文,要么切太大稀释语义
is_deleted + 级联删文档只删业务表,向量残留,检索还能召回已下架内容
model_id换模型后新旧向量混存,相似度计算无意义

3.4 向量列:先看硬上限,再谈精度

类型每维字节上限用途
vector(d)42000 维标准 float32
halfvec(d)24000 维生产默认,内存腰斩、召回几乎无损
bit(d)1/864000二值量化,先粗筛再回表精算
sparsevec(d, n)变长1000 非零SPLADE / BM25 风格稀疏

坑在这:3072 维的模型直接 vector(3072) 建表会失败,要么 halfvec(3072),要么用 MRL 截到 1024。反过来”维度越高越好”也是伪命题——1024 维在中文 RAG 上通常已经饱和,多出来的维度买的是存储和延迟。

3.5 索引:三个参数,两个上限

CREATE INDEX CONCURRENTLY ON kb_chunk_vec
  USING hnsw (embedding halfvec_cosine_ops)
  WITH (m = 16, ef_construction = 64);

SET hnsw.ef_search = 100;          -- 查询期,必须 ≥ 你的 top-k
SET maintenance_work_mem = '8GB';  -- 建索引放得下才快
坑现象修法
ef_search < top-k要 50 条只回 30 条,召回莫名偏低ef_search ≥ k,重要查询临时 SET LOCAL
metric 与 opclass 不匹配排序完全乱<=> ↔ vector_cosine_ops,<#> ↔ vector_ip_ops,<-> ↔ vector_l2_ops
建索引不加 CONCURRENTLY锁表,写入全挂低峰期并发建,且先灌数据再建索引
索引放不进 maintenance_work_mem日志报 hnsw graph no longer fits...加内存,或分区后分批建
单表一张巨图内存压不住按租户/时间分区,每分区各自 HNSW

3.6 最阴的坑:ANN 的过滤发生在”扫描之后”

-- 你以为:在这个租户里找最像的 10 条
-- 实际:先按图扫 ef_search 个近邻,再套 WHERE,返回可能只有 3 条
SELECT id FROM kb_chunk_vec v JOIN kb_chunk c USING (chunk_id)
WHERE c.kb_id = 42 AND NOT c.is_deleted
ORDER BY v.embedding <=> $1
LIMIT 10;

这是 HNSW / IVFFlat 的设计使然,不是 bug。过滤选择率越低,缺口越大。三种解法按推荐顺序:

  1. 分区或 partial index,让过滤条件变成”选哪张图”;
  2. pgvector 0.8+ 迭代扫描:SET hnsw.iterative_scan = strict_order; SET hnsw.max_scan_tuples = 20000;
  3. 先粗筛再精算:bit 列用 Hamming 距离筛 100 条,回表用 halfvec 重排。

Qdrant 的 payload 预过滤、ES 的 knn with filter、Milvus 的标量索引,解决的正是同一件事——所以”过滤能力”应该排在选型表的第一位,而不是 QPS。

3.7 如果选 Milvus 或 ES

Milvus 的四个硬约束:enable_dynamic_field 的字段以 JSON 存,建不了标量索引,高频过滤字段必须显式声明;VARCHAR 有 max_length,超了写不进;多租户优先 partition key 而不是”一租户一 collection”;delete 在流式写入下是逻辑删除,compaction 前不会立刻消失。

ES 侧则有五件事可以直接抄(ima 的做法):dense_vector + BBQ 量化(bbq_hnsw 内存优先、bbq_disk 规模优先);KNN with filter 把租户条件下推而不是事后裁剪;_source excludes 向量字段省近一半 IO;_routing 按知识库 ID 只命中相关 shard;alias + rollover 让单库涨到几亿也不慌。


4. 向量模型:榜单只能筛候选

4.1 五个真正的选型维度

维度说明常见误判
语言/领域中文、维语、法律、代码,差异远大于总分差异拿英文榜单选中文模型
维度与 MRL1024 通常够用,支持 Matryoshka 可运行时降维“维度高 = 准”
上下文长度8K 覆盖多数;32K 才有意义给整章忽略”超长会被静默截断”
稠密/稀疏/多向量一模型出三种,天然适合混合检索单独维护 BM25 却对不齐分词
部署与合规自托管 vs API:数据出域、长期成本、GPU只算 token 单价,不算 GPU 与人力

4.2 榜单不能替你决策

2026 年的公开格局大致是:中文/多语言第一梯队里,BGE-M3(568M、1024 维、8K、稠密+稀疏+多向量三合一)仍是社区默认基线;Qwen3-Embedding(0.6B/4B/8B、32K、Apache 2.0)在中文与长文档上更强;闭源侧有 Gemini Embedding、Voyage、Cohere embed-v4、OpenAI 的 text-embedding-3 系列。

但真正该警惕的是榜单口径:同一篇中文文章里,BGE-M3 的 MTEB 一处写 63.0、另一处写 65.8;Qwen3-8B 有 70.58,也有 68 左右的 C-MTEB 口径。分数不可横向比较、更新又频繁——所以它只能用来”从 20 个里筛 3 个候选”,最终必须在你自己的 100 条真实问题上比 Recall@5。

4.3 必踩的六个坑

  1. query 与 doc 预处理不一致:一边加了 instruction 前缀、一边没加;一边繁简转换、一边没转。相似度直接废掉。
  2. 归一化与度量搞混:向量已 L2 归一化时,cosine 与内积等价;没归一化却按内积排序,长文本会占便宜。
  3. 超长静默截断:模型上限 8192 token,你切了 9000 字符的中文块,尾部事实直接消失,且没有任何报错。
  4. 换模型不重建:不同模型的向量空间不兼容,混存等于随机排序。正确做法是新表/新 collection + 双写 + 评测通过再切。
  5. 拿 chunk 向量当文档向量:做文档级去重或聚类时,chunk 代表不了整篇,要单独算 doc-level(标题+摘要)向量。
  6. 忽略领域词:型号、法条编号、人名、少数民族语言专名,稠密模型天然弱——这不是换更大模型能解决的,是混合检索要解决的。

5. 解析与分块:效果上限在这里就被定死

5.1 三成事实死在入库前

传统 OCR 只”提文字”,丢掉阅读顺序、跨栏段落、无线表格、图注群组。多篇企业实践给出的量级是结构化信息丢失 30% 以上,且不可逆——后面再强的模型也救不回来。

正确顺序是:版面分析(layout)→ 阅读顺序重组 → 表格单独转 Markdown 或键值对 → 保留标题层级 → 保留页码。表格是重灾区:一张”报销标准 500 元/次”的表被切成两半,检索永远只能捞到半句。表格要么整表成块,要么把表头前置到每个子块。

清洗清单:页眉页脚页码、水印、重复版本、单位与日期格式、OCR 数字错字。数字错比错别字致命。

5.2 分块:结构感知 + 父子 + 重叠

策略适用参数起点
标题感知(按 H1/H2/条款切)技术文档、制度、规范每块 200–500 字
语义/段落切叙述性长文256–512 token,overlap 10–20%
父子块长文档child 256–384 字检索,parent 2–4K 字喂模型
按条目切FAQ、参数表一问一答一块,overlap = 0
按函数/类切代码保留签名与文件路径

成本最低、收益最高的一步:把父级标题路径拼进 chunk 正文——“第三章 权限管理 > 3.1 用户创建:管理员可以创建普通用户…”。少了这半句,模型永远不知道这段话属于哪一章。

5.3 改了分块不生效

几乎所有自建或开源系统都这样:改 chunk_size 只影响新入库文档,存量必须 reparse / reindex。这是”调了参数没效果”的第一大误会,也是很多人误判”分块策略没用”的真实原因。


6. 重排序:ROI 最高,但救不了没召回

6.1 两阶段为什么成立

embedding 是 bi-encoder:query 和 doc 各自独立编码,最后算余弦——快,但两者从未”见过面”。reranker 是 cross-encoder:把 (query, doc) 拼成一条序列过 attention,逐对打分——慢,但能判断”这段是否真的回答了这个问题”。

所以标准两段式:bi-encoder 宽召回 50–100 → cross-encoder 精排 → 取 3–8 条进上下文。

6.2 RRF 不是重排序

这是最容易被糊成一句”混合检索之后再排一下”的地方,但工程上是两件事:

RRF(倒数排名融合)Rerank(cross-encoder 精排)
输入多路召回的名次列表(query, chunk) 成对原文
计算Σ wᵢ / (k + rankᵢ),纯算术过 attention,逐对打分
看的信号只用了”排第几”,不看内容query 与 chunk 的语义交互
成本近似 0每 query × 候选数 次推理
能修什么两路分数量纲不可比“相关但不回答”

关键差别:RRF 只能把已经被召回的东西换个顺序,它不产生新判断。 一个片段两路都排第 30,融合后还在 30 附近;而 cross-encoder 读了原文之后,完全可能把它提到第 1。反过来,RRF 的第一名,cross-encoder 也可能判定跑题。

所以:RRF 解决的是融合(该信 BM25 的 0.87 还是向量的 0.62),rerank 解决的是判定(这段到底答不答这个问题)。以为”上了 RRF 就等于做了重排”,就会在”相关但不回答”的 bad case 上反复调 prompt——调不好,因为排序层根本没这个能力。

6.3 收益与代价

项量级口径
检索准确率提升+10~15 个百分点多篇企业实践
Precision 提升+20~35%阿里云 Well-Architected
额外延迟+80~120ms(100 条候选、API)阿里云 / 各家实测
自托管延迟150–300ms(T4、50 条)到 2s+(CPU、无批处理)差异极大,必须自测 p95
API 价格约 $2 / 1000 次检索(≤100 文档/次)Cohere 公开定价

6.4 模型怎么选

模型定位注意
BGE-reranker-v2-m3自托管安全默认,0.6B 多语言,8K中文强、便宜;T4 上 50 条约 0.2s
Qwen3-Reranker-8B质量优先,32K需 A100/H100 级,延迟 350ms+
Cohere Rerank 3.5 / 4托管参考实现,100+ 语言按 search 计费,数据出域
Voyage / ZeroEntropy新榜单高分榜单 ELO 多为厂商自测
Jina Reranker v2/v3效果好权重 CC-BY-NC,自托管商用要买授权

最后一行值得单独说:Jina 的 reranker 权重是 CC-BY-NC 4.0,“下载权重自己部署”在商业产品里属于未授权使用。这是最容易被忽略的合规坑——团队在笔记本上 benchmark 得很开心,然后把容器发了出去。

6.5 七个工程坑

  1. 候选数决定收益上限:只召回 10 条再 rerank,提升有限;50–100 条才是甜点。
  2. 截断:cross-encoder 输入长度有限(常见 512 / 8K),长块会被截尾——父子块在这里再赢一次。
  3. batch + fp16 / ONNX:不批处理的自托管 rerank 会把 p95 拉到秒级。
  4. 分数不可跨 query 比较:rerank score 不是概率,别设全局阈值。要阈值就用校准集定分位数,或看”top1 与 top5 的分差”。
  5. 融合方式:RRF 对分数量纲免疫,是最稳的默认;加权求和必须先归一化。
  6. rerank 不能救召回:正确片段根本没进候选池,精排只会把错的排得更整齐。先看 Recall@50,再谈 rerank。
  7. listwise 模型质量更高但更贵、更不稳定,第一版不建议上。

6.6 什么时候可以不上

这条是看了 ima 的公开架构之后补的:它的 RAG 链路止于 RRF 融合 + Top-K 截断,没有 cross-encoder 精排。 不是它不懂,是规模不允许——千万级知识库、千亿级数据,每条 query 再乘 50–100 对推理,GPU 账单和 p95 都不可接受。

可以(或应该)考虑不上的场景:

场景理由替代
超大规模、成本敏感精排成本与 QPS 线性相关靠召回 + RRF;只对 top-20 或高价值意图跑
延迟硬约束(端到端 < 300ms)API 精排本身 80–600ms小模型 / ONNX + GPU 批处理,压到 50ms
查询以精确匹配为主(编号、型号、法条)BM25 已把答案顶到第一混合权重向稀疏倾斜
知识总量小全塞上下文就够长上下文
已用大模型读后筛选生成会忽略噪声但要防 lost in the middle

而必须上的场景也很清楚:候选里大量”语义相近但不回答问题”的片段(FAQ、客服、法务条款、多版本文档),且 top-k 要压到 3–5 条、每条都很贵。

结论口径:rerank 不是必选项,是”精度收益 > 延迟与成本代价”时才成立的一笔交易。 把它写成一道算术题,而不是一句”必上”。


7. ima:千亿级怎么把 ES 用穿

来源是腾讯云开发者社区 2026-09 的一篇演讲整理(ima 高级工程师王志铭,腾讯云 × Elastic AI 搜索技术大会)。它是目前公开材料里,唯一把一个千万级知识库、近千亿数据的 AI 知识库讲到底层取舍的案例。

7.1 体量与四层架构

  • 知识库数量:千万级;ES 侧数据规模:接近千亿级。
  • 单库分布极端:多数只有几十几百个文档,极端单库数千万甚至上亿。
应用层   RAG 智能问答 / 文档内容搜索
入库层   文档解析(PDF/Word/HTML) → 智能分块 → Embedding → 写入
检索层   RAG:BM25 + Vector KNN → RRF 融合
        内容搜索:BM25 + 自定义 analyzer(拼音/ngram/高亮)
存储层   Elasticsearch 统一承载 文档 + 向量 + 倒排

一套引擎干三件事,是它最核心的取舍:不引入独立向量库,换”少一套系统要维护”。

7.2 检索链路

Query → Query 改写(多 query 扩展)
      → 双路召回:Text BM25 + Vector KNN
      → RRF 融合(ES 原生,不自己写融合逻辑)
      → Top-K 截断
      → 大模型生成

RRF 的直觉:一篇文档如果在两路召回里排名都靠前,融合后自然最前;“法国—巴黎”这种强语义关联关键词抓不到,向量补位。

注意这条链路里没有 rerank 环节——RRF 之后直接 Top-K 进模型。这不等于产品内部一定没有 cross-encoder(未披露),但可以确认的是:它把排序质量押在”召回质量 + RRF”上,而不是押在精排模型上。 对千万库、千亿数据的体量,这是成本与延迟的必然选择,也正好对上 §6.6 那张表。

有意思的对照是同一家公司的另一个产品:LKE 是明确有精排的(召回 Top-50 → 精排 Top-5),因为它服务的是”单租户、数据量小、精度优先”的企业知识库。同一厂商、两种规模、两种答案——这比任何”必上 / 不必上”的口号都有说服力。

7.3 千亿规模的三个真问题

挑战本质解法
数据无限膨胀知识库像云盘,“买书不一定看书”:存储无限、内存有限横向多集群 + 多索引路由,按 tenant_id 归属到 (cluster, index);纵向 alias + rollover,超阈值滚新索引,旧索引仍可查
向量内存与存储压力常驻内存的索引跟不上增长,冷热交替又让内存发挥不了bbq_disk(量化图落盘)+ _source excludes 向量字段,省约一半冗余存储与 IO
查询扇出与分片热点单库几万条散在几百 shard,一次查询打到所有 shard_routing 按知识库 ID 只命中相关 shard;routing_partition_size 摊开大租户;bbq_disk slice:按 tenant_id 在 shard 内再分片、独立聚类,实现租户级隔离检索

官方口径的收益:内存优化约 -71%,检索最佳 QPS 提升 58%+。

7.4 产品层怎么用

对使用者的部分也很具体:@知识库名 定向检索,可同时 @ 多个库做跨库联合;#标签 收窄范围;采集面覆盖本地文件、网页、公众号文章、微信聊天文件、腾讯文档、拍照;标签 + 自动关联 + 知识图谱;笔记与知识库互通(答案可存为笔记、生成脑图);共享库按角色分层,公开发布的”知识号”进广场且不占个人空间。

回答形态是文内引用 + 参考资料列表 + 可点回原文,并提供”仅基于知识库回答”的定向模式来压幻觉。官方说明:上传内容只用于本人的检索与推理,不用于模型训练。

7.5 能借走的三条

  1. 租户就是检索边界,不是”检索完再过滤”——这是安全与性能的双重最优。
  2. 数据无限增长时,答案是”磁盘 + 量化”,不是”加内存”。
  3. 扇出是隐形账单:一次查询打到几百个 shard,延迟和内存都会替你付钱。

8. 大厂横评:大家其实收敛到同一套

厂商/产品解析召回精排(cross-encoder)存储底座
腾讯 ima多格式 + 多模态BM25 + KNN → RRF公开链路未提ES(BBQ / 磁盘 / 多集群)
腾讯云 LKE自研 OCR,26 类文档、200MB 大文档混合检索 + Text2SQL有(Top-50 → Top-5)自建 / 云
腾讯 WeKnora(开源)docreader(OCR/Office/网页)ParadeDB BM25 + pgvector halfvec,加权 RRF有 + MMRPostgreSQL 同库
阿里百炼 / ADB结构化解析稠密 + BM25,RRF内置 cross-encoderADB-PG 等
字节火山知识库版式解析稠密 + 稀疏同库双向量有自研向量库
微软 CopilotM365 原生Graph + 混合有内部
Glean100+ 连接器自研 embedding + 混合有自建

读这张表的方式:“精排”一列不是成熟度打分。 ima 是唯一公开说明”不设 cross-encoder”的大厂案例,而它恰好是数据量最大的那个——这不是缺失,是取舍。

六条可以被当作结论的”行业公论”:

  1. 解析质量决定上限,模型决定下限。 表格、扫描件、多栏不处理,后面全是补丁。
  2. 单一向量检索不够,稠密 + 稀疏混合是生产默认。
  3. RRF 是融合,rerank 是判定;重排序性价比最高,但救不了没召回,也不是所有规模都该上。
  4. 切分要结构感知 + 父子块,固定字符数切分只是原型阶段的妥协。
  5. 权限必须在检索前生效,事后过滤不是安全边界(Glean 把它做成了产品卖点)。
  6. 没有评测集就没有优化,只有手感。

9. 24 条坑清单

P0:会造成错误答案或数据泄露

#坑后果对策
1权限在生成后过滤越权内容已进模型,可能被复述检索前按身份裁剪;过滤器由服务端构造,不信客户端参数
2新旧版本同库引用已废止条款version + effective_at,过期块不进检索
3删除不彻底已下架文档仍被召回软删 → 删向量 → 失效缓存 → 审计
4引用事后拼引用与正文对不上,比没引用更伤信任生成时绑定 chunk_id,前端可点回原文
5缓存不隔离跨租户命中同一检索缓存缓存键带 tenant:user:group:query:index_version

P1:影响效果

#坑对策
6固定字符数切分结构感知 + 父子块 + 标题路径注入
7只走向量、没有 BM25混合检索 + RRF
8ef_search < top-k参数对齐,重要查询临时放大
9过滤后召回不足分区 / 预过滤 / iterative scan
10超长块被静默截断入库时校验 token 数并告警
11换 embedding 不重建新表 + 双写 + 评测后切
12多轮对话直接检索原句query 改写 / 指代消解(“它的价格呢?”)
13top-k 越大越好精排后 3–8 条,宁少而精(lost in the middle)
14不设相似度下限最相关只有 0.3 也硬答,等于逼模型编
15没有拒答样本评测集 10–20% 为”库里没有”
16把 RRF 当精排RRF 只换名次不看原文
17不分场景硬上 rerank先算收益与 p95 成本(见 §6.6)

P2:工程与运维

#坑对策
18建索引不 CONCURRENTLY / 先建后灌先灌数据、低峰期并发建
19单表一张巨图分区 + 每分区独立 HNSW
20无 content_hash全量重灌、重复入库
21改分块不 reparse预览接口 + 存量重建任务
22无检索日志记 query / 改写 / 召回 / 精排 / 耗时 / fallback
23评估结果只在内存落盘 + 版本化,改一个变量跑一次
24只看 P50 延迟embed / rerank 要看 p95,自托管尤其

10. 评测:把”感觉”换成数字

最小可行方案:

  1. 50–100 条真实问题(从工单和日志里捞,不要自己编),每题标 gold_chunk_ids + 参考答案 + 类型(事实 / 多跳 / 对比 / 数值 / 库里没有 / 权限受限)。
  2. 10–20% 负例:库里确实没有答案,期望模型说”不知道”。不做这条,线上一定编。
  3. 检索层:Recall@k(最重要)、Precision@k、MRR、nDCG@k。参考线:Recall@5 到 0.85 再谈生成。
  4. 生成层:faithfulness(每句能否在上下文找到依据)、answer relevance、引用准确率(应接近 100%)。
  5. 一次只改一个变量:分块 → embedding → 混合 → rerank → 改写 → top-k。
  6. CI 门禁:检索指标进 PR(快、确定性强),生成指标放 nightly。

工具用 Ragas / TruLens / DeepEval / ranx 都行。但记住一句:自动指标只能告诉你”变差了”,为什么差必须人眼看 20 条 bad case。

落地顺序

阶段做什么退出条件
T0 半天选一个库(有 PG 就用 pgvector),灌 3–5 份最烂的文档引用能点回原文
T1 一天修解析与分块,开混合检索20 个真实问题能稳定命中
T2建 100 题评测集,改单变量有 Recall@5 数字
T3需要时再加 rerank / 换 embedding指标提升可复现
T4权限、增量、缓存、日志可对外小流量

11. 结论

  1. 先把责任位分清:解析、分块、索引、精排、生成,四类失败的修法完全不同。没有日志,就永远在猜。
  2. 选型看过滤与运维,不看跑分:有 PG 用 pgvector,有 ES 用 ES,百万级别上 Milvus,几十份文档别建库。
  3. 表结构一次做对:三张表、七个字段、ef_search ≥ top-k、过滤别指望”扫完再裁”。
  4. 换 embedding 等于换坐标系,必须重建;榜单只能筛候选,自测才算数。
  5. RRF 不是重排:一个换名次,一个做判定。rerank 是笔交易,不是标配。
  6. 规模会改写架构:ima 用磁盘量化 + 租户级 slice 扛千亿,LKE 用精排扛精度——同一家公司,两种答案。

知识库做得好不好,不取决于架构图画得多漂亮,而取决于你能不能回答:这条答案的依据,是从哪一段、第几页、哪个版本里出来的。


参考

一手资料

  1. 腾讯云开发者社区:《ima 基于腾讯云 ES 的千亿级 AI 知识库架构实践》,2026-09-20
  2. 腾讯云开发者社区:《腾讯云大模型知识引擎:基于 DeepSeek 实现企业知识库”快、易、省”应用落地》
  3. 腾讯云开发者社区:《腾讯云大模型知识引擎提升金融业客服问答准确率 30%》
  4. 火山引擎文档:《稀疏稠密向量混合检索》
  5. 阿里云:《Well-Architected 效能支柱》BP-PERF-06 / 07(切片、topK、重排、RRF)
  6. Milvus 官方文档:《实施多租户》《Collection Schema》
  7. pgvector / pgvectorscale 文档(类型上限、HNSW 参数、iterative scan)
  8. 腾讯 ima 官网(ima.qq.com)产品页与知识库使用指南
  9. Tencent/WeKnora 仓库与文档(v0.8.0)

实践与交叉验证

  1. 火山引擎开发者社区:《关于生产级 RAG 系统构建的一些经验总结》
  2. 《RAG 在企业知识库中的实战:我们踩过的坑和填平的路》
  3. 《从零搭建 RAG / Agent:实操路线与踩坑清单》
  4. Stochastic Sandbox: The Stack — Glean(连接器、ACL 同步、自研 embedding)
  5. 企业 RAG 权限与治理系列(预过滤 vs 后过滤、缓存隔离、审计)
  6. 各 embedding / reranker 榜单与对比(MTEB、Agentset ELO、BEIR)

说明:文中所有”提升 X 个百分点""内存 -71%“等数字均为公开口径(厂商演讲、官方文档或第三方实测),会随版本与硬件变化。涉及选型的结论建议用你自己的 100 题评测集复核一遍再落地。