Skip to content

02. ANN 索引与召回权衡

版本:v1.1

最后更新:2026-07-08

适用对象:已经知道向量数据库是做什么的,但想真正理解 exact vs approximateHNSW / IVF / Flat / 量化、召回率和延迟为什么总是在互相拉扯的工程同学

很多人第一次做向量检索时,会先问:

  • HNSW 是不是最强
  • IVF 和 Flat 到底差在哪
  • 为什么同样的 embedding,换个索引效果会变
  • 为什么写入、删除、内存和查询速度总是在互相打架

这篇不按“算法名词百科”来讲,而是从工程问题出发解释:

  • 向量索引到底在替你省什么成本
  • 为什么大多数系统不会默认做全量精确搜索
  • 什么时候应该优先追求召回率,什么时候先守住延迟和成本

1. 先把问题说清楚:索引不是为了更学术,而是为了不扫全表

给定一个查询向量,最直觉的办法是:

  1. 对库里每个向量都算一次距离。
  2. 取最相近的前 k 条。

这个办法最容易理解,也最接近“真相”,但规模一大就会遇到现实问题:

  • 向量数很大
  • 维度很高
  • 查询并发上来
  • 成本和延迟开始失控

所以索引真正做的事,是把:

  • 每次都精确全量比对

变成:

  • 尽量少看一些候选,但仍然大概率把正确邻居找回来

这就是 ANN 的工程意义。

2. Exact Search 和 ANN 到底差在哪里

2.1 Exact Search 的优点

  • 结果最接近真实最近邻
  • 便于做基线和离线对照
  • 适合小数据集或高精度回归验证

2.2 Exact Search 的代价

  • 数据规模一大,查询代价迅速上升
  • 高并发时吞吐不划算
  • 延迟更难稳定

根据 pgvector 当前官方 README,Postgres 向量检索同时支持:

  • exact nearest neighbor search
  • approximate nearest neighbor search

这本身就说明:

  • 近似检索不是“功能缩水版”,而是生产规模下的主力模式

2.3 ANN 的核心交换

ANN 不是免费午餐,它做的是典型交换:

  • 少量召回损失
  • 换更好的查询速度、吞吐和可扩展性

所以你在调索引时,实际上是在调一组业务取舍:

  • 召回率
  • top-k 命中率
  • P95 / P99 延迟
  • 内存与存储占用
  • 构建时间和更新便利性

3. 向量索引最值得先理解的四类形态

3.1 Flat

Flat 更像:

  • 没做近似压缩,直接按原始向量做精确或近似扫描

它的特点通常是:

  • 简单
  • 基线稳定
  • 小集合可用
  • 大规模成本高

根据 Weaviate 当前官方文档,flat 是明确可选的索引类型之一。

3.2 HNSW

HNSW 是目前最常见的一类 ANN 思路。

它常被工程团队喜欢,不是因为名字热门,而是因为它在很多实际数据规模下能给出不错的平衡:

  • 查询速度快
  • 召回率通常较高
  • 参数可调空间大

pgvector 当前 README 支持 HNSW 索引,Qdrant 当前官方文档也把搜索建立在 HNSW 思路上,Weaviate 也把 hnsw 作为核心索引选项之一。

3.3 IVF / IVFFlat

IVF 类方法更像先做粗分桶,再在候选桶内继续检索。

它在工程上的意义通常是:

  • 用更低的代价先缩小搜索面

pgvector 当前 README 也支持 IVFFlat,这意味着即使在 Postgres 体系里,团队也经常要在:

  • 更像数据库原生集成
  • 更像专用 ANN 调优

之间做平衡。

3.4 压缩与量化

当向量规模继续变大时,很多系统会引入:

  • 量化
  • 压缩
  • 更紧凑的索引存储

代价通常是:

  • 召回进一步受影响
  • 调试更复杂

Milvus 当前官方文档把索引类型和搜索参数分开讲,本质上就在提醒你:

  • 索引不只是一个静态建表动作,而是持续调优面

3.5 Cluster / Disk-aware 形态提醒我们:ANN 不只是一张内存图

Weaviate 当前文档除了 flat / hnsw / dynamic,还单独介绍了 HFresh 这类 cluster-based index;Milvus 当前索引家族里也已经不仅有 FLAT / IVF / HNSW,还扩展到 SCANNHNSW_SQHNSW_PQIVF_SQ8IVF_PQ 等不同压缩和检索组合。

这说明一个越来越现实的趋势:

  • ANN 不是只在“HNSW 和 IVF 二选一”

而是在向:

  • 图索引
  • 倒排分桶
  • 压缩编码
  • 磁盘 / 内存分层
  • refinement / rescoring

这些能力组合演进。

4. 不要只问“哪个索引最好”,要问“你在优化哪条链路”

工程里真正会做选择的不是“算法爱好”,而是下面这些问题:

4.1 你是读多写少,还是写入也很频繁

如果知识库变化快、增量更新频繁,就不能只测查询:

  • 构建时间
  • upsert 成本
  • 删除传播
  • 重建窗口

都会影响索引选型。

4.2 你是大单库,还是大量中小租户

Weaviate 当前官方文档提到 dynamic index 会从 flat 起步,在规模到阈值后再切向 HNSW。

这个能力特别适合:

  • 租户规模差异很大
  • 小租户没必要一上来就用重索引
  • 大租户又必须追求更高性能

4.3 你是更怕错过,还是更怕慢

不同业务对召回损失的容忍度不一样:

  • 客服问答更怕错过关键制度
  • 推荐候选池可能更能接受近似
  • 内部工具搜索更看重响应速度

所以索引参数调优,最后总会回到业务容忍度。

4.4 如果过滤是主路径,ANN 就不是一个“纯向量问题”

很多团队离线测索引时只跑:

  • query vector -> top-k

但真实系统里经常是:

  • query vector + tenant + acl + doc_type + status + date_range

这时性能和召回不只取决于 ANN 本身,还取决于:

  • 过滤发生在搜索前、中还是后
  • 过滤字段有没有被单独索引
  • 高选择性过滤会不会把 ANN 逼成近似全扫描

Azure AI Search 当前 vectorFilterMode 直接区分:

  • preFilter
  • postFilter
  • strictPostFilter

并明确指出它们会在 recall、latency、throughput 上产生不同结果。Weaviate 当前 filtering 文档也明确说明,当过滤足够严格时,会触发 flat-search cutoff,直接切到 brute-force 子集搜索。pgvector 当前 README 则提醒近似索引上的过滤是 index scan 之后 再生效,因此过滤条件可能导致返回结果变少;Qdrant 当前官方资料也反复强调 payload index 对 filtered search 的重要性。

一个更贴近生产的结论通常是:

  • filter-aware ANN 往往比“单纯换个索引名”更影响线上效果

4.5 如果内存才是瓶颈,量化不是“可选优化”,而是路线选择

向量检索上量之后,团队最先撞到的常常不是:

  • 单条查询慢一点

而是:

  • 索引太占内存
  • build 太重
  • 多租户一起放不下

Weaviate 当前专门把 vector quantization 拆成:

  • BQ
  • PQ
  • SQ
  • RQ

Milvus 当前则把 IVF_SQ8HNSW_SQHNSW_PQHNSW_PRQ 等压缩路线分成独立索引家族;Qdrant 当前也把 quantization 单列出来,说明量化不是边角功能,而是成本与规模路线的一部分。

所以量化真正回答的问题通常是:

  • 为了把索引放进可接受的内存 / 成本边界里,你愿意损失多少精度,并用多少额外候选或重排把它补回来

5. 距离度量也不是小事

很多团队只关心索引名,忽略了距离度量本身。

常见度量包括:

  • cosine similarity
  • dot product
  • euclidean distance

pgvector 当前 README 明确支持多种距离度量。

这里最重要的工程结论不是背定义,而是:

  • 建库和查询阶段要和 embedding 模型的输出假设保持一致

否则你会得到一种“看上去都在查向量,但排序总是怪怪的”的问题。

6. 为什么 ANN 调优一定要有 Exact Baseline

如果没有精确基线,你就很难回答:

  • 召回下降到底是索引造成的
  • 还是 embedding / chunk / filter / query rewrite 造成的

一个更稳的做法通常是:

  1. 先在一部分黄金集上跑 exact baseline。
  2. 记录 Recall@kHit@k、top1 命中率。
  3. 再切 ANN,比较损失和延迟收益。

这样你调的就不是“玄学效果”,而是可解释权衡。

6.1 Exact baseline 最好按 query bucket 分层跑

如果你只拿一个混合大盘平均值做 baseline,很容易把真正的问题冲掉。

更稳的做法通常是至少拆成这些桶:

  • 纯语义问题
  • 编号 / 错误码 / 人名 / 产品名问题
  • 高过滤选择性问题
  • 大租户和小租户问题
  • 热数据和冷数据问题

因为 ANN 参数带来的收益和损失,经常只在某几个 query bucket 里特别明显。

6.2 过滤型查询应该有自己的 exact baseline

Azure AI Search 当前过滤文档、Weaviate 当前 filtering 文档、pgvector 当前 README 都在指向同一个现实:

  • 带过滤的 ANN,不等于不带过滤的 ANN

所以更稳的基线不是只测:

  • unfiltered Recall@k

而是至少还要测:

  • filtered Recall@k
  • result_count under filter
  • 高选择性过滤下的 P95

否则你会出现一种典型误判:

  • 离线 ANN 看起来很好
  • 一加 tenant / acl,线上马上掉结果

7. 调 ANN 索引时最常见的四种误判

7.1 把 chunk 问题误判成索引问题

如果 chunk 太碎或太脏,再强的 ANN 也只能把脏候选找得更快。

7.2 把过滤问题误判成索引问题

很多“召回差”其实是:

  • filter 过严
  • tenant / acl 错了
  • 版本字段把正确文档过滤掉了

7.3 把重排缺失误判成索引问题

索引通常负责候选池,不一定负责最终 top1 最优。

如果候选池里已经有正确项,但最终顺序不稳,更像:

  • rerank 问题
  • hybrid 融合问题
  • 引证组装问题

7.4 把 embedding 变更误判成索引波动

Azure AI Search 当前文档强调查询向量和索引向量应在同一 embedding space。

这意味着如果你换了 embedding 模型却没重建索引,问题根本不在 ANN 参数。

8. 不同系统文档给出的工程启发

8.1 pgvector

pgvector 的价值不只是“Postgres 里也能搜向量”,还在于它让你看见一种非常实用的路线:

  • 当你已经强依赖事务、JOIN、结构化查询时,不一定非得先上独立向量数据库

pgvector 当前 README 还把参数和运维边界写得非常直接:

  • HNSW 主要看 mef_constructionhnsw.ef_search
  • IVFFlat 主要看 listsivfflat.probes
  • HNSW 通常比 IVFFlat recall / speed tradeoff 更好,但 build 更慢、内存更高
  • IVFFlat 没有训练前提时效果容易不稳,尤其是在数据还太少时建索引

这给工程团队一个很实用的调优顺序:

  1. 先别急着改建索引参数。
  2. 先扫 query 期参数,例如 ef_searchprobes
  3. 只在收益证据明确时再动 mef_constructionlists 这类 build 级参数。

因为 query 参数通常更适合拿来做:

  • 灰度
  • A/B
  • 单查询 override

而 build 参数一旦改动,往往意味着:

  • rebuild 成本
  • 更长维护窗口
  • 更重内存占用

8.1.1 pgvector 对过滤的提醒尤其值得重视

pgvector 当前 README 明确提醒:

  • approximate index 上的过滤是在扫描后生效
  • 过滤条件可能让返回结果少于预期
  • 可以开启 iterative index scans 自动扩大扫描范围
  • 对少数值过滤可以考虑 partial index
  • 对大量不同值过滤可以考虑 partitioning

这说明在 Postgres 路线里,过滤问题往往不该只靠调 ANN,而是要和:

  • 普通列索引
  • partial index
  • 分区设计
  • iterative scans

一起看。

8.2 Pinecone

Pinecone 的文档更强调:

  • 数据建模
  • metadata
  • multitenancy
  • production checklist

这提醒我们:

  • 对托管向量库来说,生产问题常常比索引名字更早出现

8.3 Weaviate

Weaviate 当前文档把:

  • flat / hnsw / dynamic
  • named vectors
  • hybrid search
  • multi-tenancy

放在同一个体系里,适合用来理解“索引只是检索架构的一部分”。

更进一步说,Weaviate 当前文档给了三个特别实用的启发:

  1. dynamic index 可以从 flat 自动切到 hnsw,默认阈值是 10,000 对象。
  2. 对限制很强的 filter,系统可能触发 flat-search cutoff,直接在子集上 brute-force。
  3. 向量压缩不是一个点状开关,而是一整组 BQ / PQ / SQ / RQ 路线。

这三点放在一起看,特别适合帮助团队建立下面这个判断:

  • 不是所有租户、所有 collection、所有过滤条件都应该用同一套 ANN 策略

8.4 Milvus / Qdrant

Milvus 和 Qdrant 的文档都非常适合理解:

  • 专用向量检索系统为什么会把索引、过滤、存储和搜索参数拆得很细

对工程团队来说,这类系统的价值常在于:

  • 让你更细地控制性能与召回

8.4.1 Qdrant:先把 payload index 想清楚,再谈 HNSW 调优

Qdrant 当前资料里最值得抓住的,不只是 HNSW 本身,而是它把:

  • vector index
  • payload index
  • quantization
  • hybrid queries

放在一整条链路里讲。

尤其是 Qdrant 当前 indexing / filtering 文档反复强调:

  • 只有 vector index 还不够
  • 过滤字段最好建 payload index
  • payload index 还能帮助系统估计 filter cardinality,参与 query planning

这件事非常关键,因为很多线上 filtered ANN 问题,首先该问的不是:

  • HNSW 要不要调

而是:

  • 过滤字段到底有没有被单独索引

另外,Qdrant 当前搜索文档还提到 ACORN search algorithm 这类针对严格过滤的路径,这再次说明:

  • 过滤和 ANN 在生产系统里是强耦合问题

8.4.2 Qdrant 的量化启发

Qdrant 当前 quantization 文档说明,量化表示可以:

  • 作为快速候选选择层
  • 也可以直接参与搜索

这提醒团队一个很实用的现实:

  • 量化之后别只看“是不是更省了”
  • 还要看是否需要保留原向量做 rescoring / refinement

否则很容易出现:

  • 成本降了
  • 但 top-k 候选质量已经不够下游 rerank 补救

8.4.3 Milvus:压缩索引家族本身就是路线选择

Milvus 当前资料的价值在于,它把“ANN 是一整个工程面”讲得非常展开。

从官方索引列表看,Milvus 已经不只是:

  • FLAT
  • IVF_FLAT
  • HNSW

还包括:

  • IVF_SQ8
  • IVF_PQ
  • HNSW_SQ
  • HNSW_PQ
  • HNSW_PRQ

这说明 Milvus 特别适合帮助我们理解两层现实:

  1. 索引选择本身已经内含压缩路线。
  2. 压缩路线往往还需要 refinement 才能把 recall 拉回来。

换句话说,Milvus 给团队最大的启发不是“可选项很多”,而是:

  • 生产 ANN 不是一个索引类型,而是一条 coarse filtering -> compressed retrieval -> refinement 的分层链路

8.5 Azure AI Search:同一系统里就能做 ANN vs exhaustive 对照

Azure AI Search 当前文档非常适合理解企业检索里 ANN 的另一个现实:

  • 你可能要同时面对 HNSWexhaustive KNNhybridvectorFilterModesemantic ranker

它的一个很重要的工程启发是:

  • exhaustive = true 不只是查询模式,也很适合拿来做线上对照基线

也就是说,你不一定要单独准备一套完全独立的 exact 离线环境,很多时候可以直接在同一系统里对比:

  • HNSW
  • exhaustive KNN

再去量化 recall 损失和延迟收益。

另外,Azure 当前过滤文档把 preFilter / postFilter / strictPostFilter 的代价写得很明确:

  • preFilter 通常更保 recall,但高选择性过滤会更耗 CPU / latency
  • postFilterstrictPostFilter 更容易出现 false negatives

所以 Azure 路线里一个很常见的误判就是:

  • 以为 filter mode 只是实现细节

实际上它可能直接改变:

  • top-k 是否够用
  • selective filter 下的丢结果概率
  • semantic ranker 前能拿到多少候选

9. 一个更实用的 ANN 调优流程

建议不要直接在线上盲调。

更稳的顺序通常是:

  1. 固定一批 query 黄金集。
  2. 固定 embedding 模型和 chunk 策略。
  3. 用 exact baseline 跑出理论上限。
  4. 比较不同索引类型或参数下的 Recall@k
  5. 再比较 P95 延迟、构建时长和内存占用。
  6. 最后再看业务侧答案指标是否真的变好。

这样能避免一种常见误区:

  • 只因为某个参数让向量召回更快,就误以为整个 RAG 系统更好了

9.1 先调 search 参数,再动 build 参数

一个更稳的顺序通常是:

  1. 先固定数据与 query bucket。
  2. 先调 query 期参数,例如 ef_searchprobesoversampling
  3. 确认收益之后,再动 build 期参数,例如 mef_constructionlists 或压缩配置。

因为 query 参数通常更适合:

  • 小范围验证
  • 租户分层
  • 路由策略实验

而 build 参数更接近:

  • 结构性变更

9.2 对 IVF,先定 lists,再扫 probes

pgvector 当前 README 给了非常实用的经验起点:

  • lists 可以从 rows / 1000sqrt(rows) 这类量级开始
  • probes 可以从 sqrt(lists) 起步

这背后的思路非常重要:

  • lists 更像库级结构
  • probes 更像查询期搜索半径

所以很多时候不该一上来就频繁重建 IVF,而是先确认:

  • 当前 lists 下,probes 的可接受工作点到底在哪

9.3 量化场景记得留“补精度”的候选空间

不管是 Weaviate 的 quantization、Qdrant 的 quantized representation,还是 Milvus 的 IVF_SQ8 / HNSW_PQ 路线,本质都在提醒:

  • 压缩会带来信息损失

因此更稳的经验通常是:

  • 压缩索引时适当放大候选池
  • 让 rerank / refinement 在更高精度上做最后裁决

如果量化后还把 top-k 压得很死,就很容易出现:

  • 搜得很快
  • 但正确答案从候选阶段就已经丢了

9.4 过滤型 query 要单独建调优桶

不要把:

  • 无过滤 query
  • 低选择性过滤 query
  • 高选择性过滤 query

放在同一桶里看平均值。

因为 Azure filter mode、Weaviate flat-search cutoff、pgvector iterative scans、Qdrant payload indexes 都在说明:

  • 过滤型 query 的最优参数,常常和普通语义 query 不同

9.5 Rebuild 不是事故,而是计划动作

ANN 线上治理里很常见但容易被忽略的一件事是:

  • rebuild 不是“出问题才做”

而应该提前规划:

  • embedding 升级要不要整库重建
  • quantization 切换怎么做双轨
  • 索引 build 期间如何降级
  • 谁来判断回滚

pgvector 当前 README 甚至直接把 build time、parallel workers、maintenance_work_mem、reindex、vacuum 都写进日常运维路径里,这说明 ANN 索引本身就是需要被运营的生产资产。

10. 至少要观察的 6 个索引指标

10.1 召回指标

  • Recall@k
  • Hit@k
  • top1 / top3 命中率

10.2 延迟指标

  • 平均延迟
  • P95 / P99 延迟
  • 高过滤条件下的延迟波动

10.3 成本指标

  • 内存占用
  • 存储占用
  • 索引构建时间
  • 重建成本

10.4 更新指标

  • upsert 吞吐
  • 删除完成时间
  • 重建窗口长度

10.5 租户指标

  • 大租户和小租户是否共享同样代价
  • 热租户是否拖垮整体延迟

10.6 业务指标

  • 最终答案引用正确率
  • 混合检索胜率
  • rerank 提升幅度

10.7 过滤与候选充足度指标

  • selective filter 下的返回结果不足率
  • candidate_count / requested_k
  • iterative scan 触发率
  • brute-force cutoff 触发率
  • exact 对照下的 filtered false negative rate

11. 常见反模式

  • 没有 exact baseline,就直接宣称某种 ANN 参数更好。
  • 只在单条查询上“感觉更快”,不看黄金集分布。
  • 只看向量召回,不看 metadata filter 对延迟和正确率的影响。
  • 索引刚调完就换 embedding,导致结果不可比较。
  • 只测冷启动,不测持续写入、删除和重建。
  • 过滤慢就先调 HNSW,却没先给过滤字段建 payload / inverted / scalar index。
  • 一上量就开量化,却没有给候选放宽或保留 refinement。
  • 用 unfiltered recall 代表线上带 acl / tenant 的真实效果。
  • IVF 在数据还太少时就建很多 lists,后面再把召回问题怪给算法。
  • 把 build 参数当成随手可改的开关,却没有预留 rebuild 和回滚窗口。

12. 实战判断题:什么时候先别碰索引

如果你现在遇到的是下面几类问题,先别急着调 ANN:

  • 正确文档根本没入库
  • chunk 设计明显不合理
  • metadata 字段不完整
  • namespace / tenant / acl 有串数风险
  • 纯向量本来就不适合当前 query 类型

这些问题没先收住,索引优化通常只会让错误更稳定。

13. 推荐搭配阅读

14. 重点官方资料

以下资源已按 2026-07-08 复核可访问: