向量数据库与检索索引选型

向量检索的难点不在把向量存进去,而在十亿级数据下同时守住召回率、延迟与内存三条线。本文深入 HNSW、IVF-PQ、DiskANN 三类索引的原理与参数,对比 pgvector、Milvus、Qdrant 的适用边界,给出维度选择、元数据过滤、混合检索、增量更新与索引重建的工程方案,附内存估算公式与可运行的调参脚本。

向量数据库的选型和调优,是 RAG 系统里最容易被「跑得通就行」掩盖的一块。demo 阶段用 pgvector 存一万条向量,查询十毫秒返回,一切都很美好;等到线上数据涨到千万级、并发涨到几百 QPS,才发现召回率悄悄掉到 60%、内存账单翻了三倍、删除一条文档要等索引重建。这些问题都不是「换个库」能解决的,而是索引算法本身的取舍没有想清楚。

本文与 RAG 检索服务的工程化 划清边界:那篇讲的是端到端的 RAG 链路(分块、拼接、生成、缓存),索引只是其中一环;本文则把镜头推到索引内部,讲清 HNSW、IVF-PQ、DiskANN 三类算法各自的适用场景与参数含义,以及向量库在数据规模增长时会遇到的增量更新、删除、内存爆炸等具体问题。

理解向量检索只需要抓住一个三角:召回率、延迟、内存。三者不可能同时最优,任何索引算法都是在三角中选一条边。选型的本质是判断自己的业务更在乎哪条边,然后用参数把这条边守死。本文按这个逻辑展开:先建立三角认知,再逐个拆解算法,然后是选型、调参、运维,最后给出内存估算与调参脚本。

目录

  1. 向量检索的三角约束
  2. 索引算法全景与精度对照
  3. HNSW:图层结构与参数
  4. IVF-PQ:倒排与乘积量化
  5. DiskANN:磁盘驻留的十亿级方案
  6. 向量库选型:pgvector、Milvus 与 Qdrant
  7. 嵌入模型与维度选择
  8. 元数据过滤与多租户隔离
  9. 混合检索与重排的接入
  10. 增量更新、删除与索引重建
  11. 内存估算与成本核算
  12. 权衡取舍
  13. 常见坑清单
  14. 小结

1. 向量检索的三角约束

精确最近邻(KNN)需要把查询向量与库中每一条向量算一遍距离,复杂度 O(N·d)。一千万条 1024 维向量,单次查询要算一百亿次浮点乘加,延迟以秒计——这在交互式场景完全不可接受。近似最近邻(ANN)用「牺牲一点召回率换数量级加速」的契约换取可用性,把延迟压到毫秒级。

三角的三个顶点:

  • 召回率:ANN 返回的 top_k 里,真正属于精确 top_k 的比例。Recall@10 = 0.95 意味着有 5% 的相关文档被漏掉。
  • 延迟:单次查询耗时,通常看 P95 与 P99 而非均值,因为长尾决定了用户体验。
  • 内存:索引常驻内存的大小,由向量维度、条数与索引结构的额外开销决定。
业务诉求应该守住的边典型参数取向
强合规检索(法务、医疗)召回率优先efSearch 拉高,接受延迟
高并发在线问答延迟优先efSearch 适中,用缓存补召回
十亿级冷数据内存优先量化或磁盘索引,召回率换内存
中小规模(百万内)三者兼顾HNSW 默认参数即可

一个反直觉但重要的结论:召回率不足时,加 top_k 往往比调索引参数更划算。把 efSearch 从 64 提到 256,延迟翻倍、召回率涨 3 个点;而把 top_k 从 10 提到 50 再交给重排,几乎不增加检索延迟却能补回大部分漏召回。这也是 混合检索与重排 成为标配的原因。

2. 索引算法全景与精度对照

ANN 索引分两大流派:基于图的(HNSW、DiskANN)和基于量化的(IVF、PQ、IVF-PQ)。

算法核心思想召回率延迟内存支持删除适用规模
Flat(暴力)全量精确计算100%极高1×易十万以内
IVF-Flat聚类分桶,只搜部分桶高低1×易百万级
IVF-PQ分桶 + 乘积量化压缩中低1/4 到 1/32易千万到十亿
HNSW多层近邻图,贪心下降很高极低1.5× 到 2×难百万到千万
DiskANN图索引 + SSD 驻留高中1/10 到 1/20中十亿级

这张表的关键信息有两行:HNSW 召回率与延迟最好但内存最贵、删除最难;IVF-PQ 内存最省但召回率有损。真实系统里最常见的组合是「HNSW 做在线热数据 + IVF-PQ 做历史冷数据」,用两套索引分层。

选型的第一问是数据规模。十万以内直接用 Flat,别引入任何近似误差;百万级用 HNSW;千万级以上必须在 HNSW 与 IVF-PQ 之间做取舍;十亿级且内存装不下时,DiskANN 是唯一现实选择。

3. HNSW:图层结构与参数

HNSW(Hierarchical Navigable Small World)是当前在线检索的主力。它把向量组织成一张多层图:上层稀疏、边跨度大,用于快速接近目标区域;下层稠密、边跨度小,用于精细定位。查询从最上层的入口点开始,每层贪心走向距离查询最近的邻居,然后下沉到下一层继续,直到最底层返回结果。

第 3 层   ●───────────────●            稀疏,快速跳转
第 2 层   ●────●──────────●────●
第 1 层   ●──●──●───●─────●──●──●
第 0 层   ●●○●●●○●●●●○●●●●●●●●○●●   稠密,包含全部向量
                 ↑ 查询从顶层入口下降,逐层逼近

三个核心参数:

参数含义典型值增大影响调优建议
M每个节点的最大连接数16 - 64召回高、内存大、建索引慢16 起步,内存允许到 32
efConstruction建索引时的候选队列100 - 500图质量高、建得慢离线可设 256
efSearch查询时的候选队列64 - 256召回高、延迟高从 64 扫到甜点

HNSW 的内存开销公式近似为:

内存 ≈ N × (d × 4 + M × 2 × 4) 字节
      = N × (向量本体 + 图边开销)

例:1000 万条 × 1024 维 float32
    向量本体 = 1000万 × 1024 × 4 = 40.9 GB
    图边开销 (M=32) = 1000万 × 32 × 2 × 4 = 2.56 GB
    合计约 43.5 GB,再加框架开销与副本,单机需 64 GB 以上

注意 M × 2 里的 2 表示每层平均连接数约为 2M(第 0 层连接数上限是 2M,上层是 M)。这个细节解释了为什么「M 从 16 调到 32」会让内存涨得比预期快。

HNSW 的删除是它最大的软肋。图中的边是双向的,删一个节点需要修复所有指向它的边,成本高。多数实现(包括 pgvector 早期版本)用「标记删除 + 定期重建」来绕开,表现为:删除后内存不释放、查询结果里过滤掉已删节点、召回率随删除比例上升而下降。当删除比例超过 20% 时,就应该触发索引重建。

4. IVF-PQ:倒排与乘积量化

IVF(Inverted File)先用 k-means 把向量空间聚成 nlist 个簇,查询时只搜索距离最近的 nprobe 个簇。PQ(Product Quantization)则把高维向量切成若干子段,每段独立做 k-means 量化到 256 个码字,用 1 字节表示一段。

原始向量:1024 维 float32 = 4096 字节
PQ 压缩:切成 128 段 × 每段 8 维 → 每段 8 bit 码字
         = 128 字节(压缩 32 倍)

IVF-PQ 的内存因此可以压到原始的四分之一到三十二分之一,这是它支撑十亿级数据的根本原因。参数与取舍:

参数含义典型值增大影响
nlist簇的数量sqrt(N) 到 4×sqrt(N)簇更细,但每簇样本少
nprobe查询搜索的簇数8 - 64召回高、延迟高
M(子段数)向量切分份数d/8 到 d/4精度高、压缩率低
nbits每段码字位数8一般固定 8

nlist 的经验值是 4 × sqrt(N):一千万条数据取约 12650 个簇。nprobe 决定召回率,从 nlist 的 1% 起步往上扫。

PQ 的代价是精度损失明显:量化后的距离是近似的,召回率通常比 HNSW 低 5 到 15 个百分点。补偿手段是「重排」——用 IVF-PQ 快速召回一个较大的候选集(如 top_200),再用原始向量或交叉编码器精排到 top_10。这就是经典的两阶段检索架构。

IVF-PQ 的另一个优势是更新友好:新增向量只需分配到最近的簇,删除只需从簇的倒排列表中移除,不需要重建整个图。因此它对「频繁增删」的场景比 HNSW 更合适。

nprobe 与召回率的实测曲线

nprobe 是 IVF-PQ 唯一需要在线调的参数。下面这段脚本扫描 nprobe 并记录召回率与 P95 延迟,用于找甜点:

import time

def sweep_nprobe(index, queries, ground_truth, nprobe_values, top_k: int = 10):
    """扫描 nprobe,返回每个取值下的召回率与 P95 延迟"""
    rows = []
    for nprobe in nprobe_values:
        index.set_nprobe(nprobe)                 # Milvus/Faiss 均支持动态设置
        hits, latencies = 0, []
        for q, truth in zip(queries, ground_truth):
            t0 = time.perf_counter()
            res = index.search(q, top_k)
            latencies.append((time.perf_counter() - t0) * 1000)
            hits += len(set(r[0] for r in res) & set(truth))
        recall = hits / (len(queries) * top_k)
        p95 = sorted(latencies)[int(len(latencies) * 0.95) - 1]
        rows.append({"nprobe": nprobe, "recall": round(recall, 4),
                     "p95_ms": round(p95, 2)})
    return rows

if __name__ == "__main__":
    for row in sweep_nprobe(None, [], [], [1, 4, 8, 16, 32, 64]):
        print(row)

经验数据(5000 万条 1024 维,nlist=16384,PQ M=128):nprobe=8 时召回率约 71%,nprobe=16 涨到 84%,nprobe=32 到 89%,之后收益迅速递减而延迟线性上升。因此 16 到 32 是常用工作区间,具体拐点必须在自己的数据上重跑。

5. DiskANN:磁盘驻留的十亿级方案

当向量规模涨到十亿级,即使 PQ 压缩后也装不进内存。DiskANN(微软提出,工业界代表是 Milvus 的 DiskANN 与 Qdrant 的 on-disk 模式)的思路是:把完整的图索引与向量放在 SSD 上,只在内存里保留一个压缩后的导航图。

它的结构是「内存中的 PQ 近似图 + 磁盘上的全精度邻居列表」。查询时先在内存图上做粗略导航,定位到候选区域后从 SSD 读取原始向量做精算。因为 SSD 的随机读延迟在几十微秒量级,配合预取与批量读,单次查询只触发几十到几百次磁盘读,延迟能控制在 10 到 50 毫秒。

指标全内存 HNSWDiskANN
内存占用(10 亿条 1024 维)约 4.4 TB约 200 GB
单查询延迟 P955 - 15 ms20 - 50 ms
硬件要求大内存机器本地 NVMe SSD
适用千万级热数据十亿级温冷数据

DiskANN 的取舍很直白:用可接受的延迟增加换取一个数量级的内存下降。它的前提是本地 NVMe——把索引放在网络存储(如 EBS、NAS)上会让延迟飙升到不可用。

6. 向量库选型:pgvector、Milvus 与 Qdrant

算法选定后,落地还要选一个具体的库。三者的定位差异很大:

维度pgvectorQdrantMilvus
部署复杂度极低(Postgres 扩展)低(单二进制)高(依赖 etcd、对象存储)
索引类型HNSW、IVF-FlatHNSW、量化HNSW、IVF、DiskANN、GPU
过滤能力极强(完整 SQL)强(payload 过滤)强
混合检索需自建(tsvector 加向量)原生稀疏向量原生 BM25
分布式靠 Postgres 方案分片复制原生分布式
推荐规模百万级千万级十亿级
运维成本几乎为零低高

选型的第一原则是跟随现有技术栈:

  • 已经用 Postgres,且向量在百万级以内:选 pgvector。它的最大优势不是性能,而是向量与业务数据在同一个事务里,能直接 JOIN、直接 WHERE、直接备份恢复,省掉一整套数据同步逻辑。
  • 需要独立向量服务、规模到千万级、想要更省心的运维:选 Qdrant。单二进制部署、原生过滤与量化、支持稀疏向量做混合检索。
  • 十亿级、需要分布式与 GPU 索引、团队有专门的中间件运维能力:选 Milvus。

一个常见的错误是「为了未来的规模提前上 Milvus」。Milvus 的组件多、依赖重,在百万级数据上它带来的复杂度远大于收益。规模真正到千万级再迁移,成本远低于一开始就背上运维包袱。

pgvector 的索引创建与查询示例:

-- PostgreSQL 16 + pgvector 0.7
CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE doc_chunks (
    id        bigserial PRIMARY KEY,
    tenant_id text      NOT NULL,
    doc_id    bigint    NOT NULL,
    content   text      NOT NULL,
    embedding vector(1024) NOT NULL
);

-- HNSW 索引:余弦距离,参数与前面讨论一致
CREATE INDEX idx_chunks_hnsw ON doc_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 32, ef_construction = 256);

-- 查询时设置 ef_search(会话级),并做租户前置过滤
SET hnsw.ef_search = 128;

SELECT id, content, 1 - (embedding <=> $1) AS score
FROM doc_chunks
WHERE tenant_id = 't_8821'          -- 前置过滤,走 B-tree 先缩小范围
ORDER BY embedding <=> $1
LIMIT 10;

注意 WHERE tenant_id = ... 与 ORDER BY 的配合:pgvector 在过滤选择性高时会先走过滤再走索引,选择性低时可能退化为全表扫描。生产环境应把租户过滤做成分区(按 tenant_id 哈希分区),让每个分区的索引天然只含本租户数据,既保证隔离又保证性能。

7. 嵌入模型与维度选择

维度直接决定存储与内存,是所有成本的源头。

维度1000 万条 float32含 HNSW 开销(约 1.1×)单机可行性
38415.4 GB17 GB单机 32 GB
76830.7 GB34 GB单机 64 GB
102440.9 GB45 GB单机 64 GB 紧张
153661.4 GB68 GB需 96 GB 以上
3072122.9 GB135 GB需分片

两个降本手段:

  • Matryoshka 降维:许多现代嵌入模型(如 text-embedding-3、bge-m3)支持「截断维度」。把 3072 维截到 1024 维,检索质量损失通常小于 1 个百分点,而存储直接降到三分之一。这是大规模场景最划算的一步优化。
  • 向量量化:把 float32 换成 int8 或 binary。int8 标量量化省 4 倍内存、召回损失约 1%;binary 量化(1 bit)省 32 倍,但需要配合重排才能用。Qdrant 与 Milvus 都支持在 HNSW 上叠加标量量化。

选型的顺序是:先定嵌入模型(受中文能力、上下文长度、许可约束),再定维度(在质量与内存间取平衡),最后决定是否量化。查询与文档必须用同一个模型、同一个维度、同一个归一化方式,这是不可妥协的硬约束。

8. 元数据过滤与多租户隔离

生产环境几乎没有「纯向量检索」——总要按租户、时间、权限、文档类型过滤。过滤的实现方式对性能影响巨大:

方式实现延迟正确性风险
后置过滤先检索 top_k,再按条件筛低过滤后结果不足
前置过滤检索时即带过滤条件略高需索引支持
分区隔离每租户独立集合或分区最低租户多则元数据膨胀

后置过滤在小占比租户上会彻底失效:如果某租户数据只占全库 0.5%,检索 top_100 期望只能命中 0.5 条,几乎必然返回空。这不是性能问题,而是正确性问题,多租户场景必须用前置过滤或分区隔离。

Qdrant 的前置过滤示例:

from qdrant_client import QdrantClient
from qdrant_client.models import Filter, FieldCondition, MatchValue, Range

client = QdrantClient(url="http://qdrant:6333")

hits = client.search(
    collection_name="doc_chunks",
    query_vector=qvec,
    query_filter=Filter(must=[
        FieldCondition(key="tenant_id", match=MatchValue(value="t_8821")),
        FieldCondition(key="ts", range=Range(gte=1735689600)),  # 2025-01-01 之后
    ]),
    limit=10,
    with_payload=True,
)

为了让前置过滤高效,被过滤的字段应该建 payload 索引:

from qdrant_client.models import PayloadSchemaType

client.create_payload_index("doc_chunks", "tenant_id", PayloadSchemaType.KEYWORD)
client.create_payload_index("doc_chunks", "ts", PayloadSchemaType.INTEGER)

没有 payload 索引时,过滤会退化成对每个候选做一次字段比较,在高基数过滤字段上延迟会成倍上升。

9. 混合检索与重排的接入

纯向量检索对精确匹配(错误码、编号、专有名词)不敏感,需要 BM25 补足。混合检索有两种落地方式:

  • 库内融合:Qdrant 支持稀疏向量(SPLADE 或 BM25 编码),Milvus 内置 BM25 函数,可在一次查询里返回两路结果并加权。
  • 应用层融合:分别查向量库与搜索引擎(Elasticsearch、OpenSearch),用 RRF 在应用层合并。灵活但多一次网络往返。

RRF(Reciprocal Rank Fusion)是融合的标准做法,因为它不需要归一化两路分数:

from collections import defaultdict

def rrf_fuse(ranked_lists: list[list[str]], k: int = 60) -> list[str]:
    """对多路召回结果做倒数排名融合,k 取 60 是文献常用值"""
    scores: dict[str, float] = defaultdict(float)
    for ranked in ranked_lists:
        for rank, doc_id in enumerate(ranked, start=1):
            scores[doc_id] += 1.0 / (k + rank)
    return [d for d, _ in sorted(scores.items(), key=lambda kv: kv[1], reverse=True)]

if __name__ == "__main__":
    vec = ["d1", "d2", "d3", "d4"]
    bm25 = ["d3", "d5", "d1", "d6"]
    print(rrf_fuse([vec, bm25])[:4])

融合之后必须重排。索引的职责是「不漏」,把候选集做大到 50 到 200 条;重排的职责是「准」,用交叉编码器把候选压到 3 到 5 条喂给 LLM。把这两个职责混在一层,要么召回不足,要么上下文被噪声淹没。

10. 增量更新、删除与索引重建

这是向量库运维最痛的部分,因为不同索引对更新的友好度差异极大。

索引新增删除更新重建成本
Flat追加直接删直接改无
IVF-Flat / IVF-PQ分配到簇从倒排移除删加低
HNSW插入并连边标记删除删加高
DiskANN追加到内存段标记删除删加高

HNSW 的更新策略在实践中分三步走:

  1. 小批量增量:新数据先写入一个小的「增量索引」(可以用 Flat 或小 HNSW),查询时同时查主索引与增量索引再合并。增量索引通常控制在主索引的 5% 到 10%。
  2. 定期合并:增量索引涨到阈值后,与主索引合并重建。合并是离线动作,用后台任务在低峰期做。
  3. 删除用墓碑:删除时只标记,查询结果里过滤掉。当墓碑比例超过 20% 时触发重建。
"""index_compaction.py 增量索引的合并阈值判断"""
from dataclasses import dataclass

@dataclass
class IndexStats:
    main_size: int          # 主索引条数
    delta_size: int         # 增量索引条数
    tombstone: int          # 标记删除条数

    @property
    def delta_ratio(self) -> float:
        return self.delta_size / max(self.main_size, 1)

    @property
    def tombstone_ratio(self) -> float:
        return self.tombstone / max(self.main_size + self.delta_size, 1)

    def need_compaction(self) -> tuple[bool, str]:
        if self.delta_ratio > 0.10:
            return True, f"增量占比 {self.delta_ratio:.1%} 超阈值 10%"
        if self.tombstone_ratio > 0.20:
            return True, f"墓碑占比 {self.tombstone_ratio:.1%} 超阈值 20%"
        return False, "健康"

if __name__ == "__main__":
    s = IndexStats(main_size=10_000_000, delta_size=1_250_000, tombstone=300_000)
    print(s.need_compaction())

重建的代价常被低估:一千万条 1024 维向量的 HNSW 重建,单机需要几十分钟到数小时,期间查询质量下降、CPU 打满。因此重建必须设计成可滚动、可回滚的流程,而不是一次「停机重建」。

11. 内存估算与成本核算

把前面的公式串起来做一次真实核算。目标:服务 5000 万条 1024 维向量,要求 P95 延迟低于 30 毫秒,内存可控。

方案 A:全内存 HNSW (M=32)
  向量本体   5000万 × 1024 × 4 = 204.8 GB
  图边开销   5000万 × 32 × 2 × 4 = 12.8 GB
  合计约     218 GB  → 需要 3 台 128 GB 机器做分片,成本高

方案 B:HNSW + int8 标量量化
  向量本体   5000万 × 1024 × 1 = 51.2 GB
  图边开销   12.8 GB
  合计约     64 GB  → 单台 128 GB 机器可容纳,召回损失约 1%

方案 C:IVF-PQ (M=128 子段)
  压缩后     5000万 × 128 = 6.4 GB
  倒排开销   约 2 GB
  合计约     9 GB  → 单机轻松,召回率需靠重排补回

三个方案的延迟与召回对照(实测口径,单机 NVMe,并发 64):

方案内存Recall@10P95 延迟适用
A 全内存 HNSW218 GB97.5%8 ms召回优先、预算充足
B HNSW + int864 GB96.3%12 ms平衡点,推荐
C IVF-PQ9 GB84.1%6 ms内存极紧,需重排

方案 B 通常是性价比最高的选择:用 1 个百分点的召回损失换取 3.4 倍的内存下降。方案 C 的低延迟有欺骗性——它的 84% 召回意味着最终质量要靠重排补,而重排的延迟往往超过索引本身省下的时间。

成本核算的完整口径还应包含:副本数(生产至少 2 副本,内存翻倍)、增量索引的额外开销(约 10%)、以及重建时的临时双份内存。把这三项算进去,方案 B 的实际内存需求约 140 GB,需要一台 192 GB 的机器或两台 128 GB 分片。

权衡取舍

决策点选 A选 B判断依据
索引算法HNSW(召回延迟优先)IVF-PQ(内存优先)数据规模与内存预算
部署形态pgvector(跟随栈)独立向量库(规模)是否已在用 Postgres
维度高维(质量优先)降维(成本优先)质量损失能否接受
量化不量化(召回优先)int8/binary(成本优先)内存是否触顶
过滤前置过滤(正确性)分区隔离(性能)租户数与数据占比
更新增量索引(在线)定期重建(离线)写入频率与容忍窗口

一句话原则:先按规模定算法,再按栈定产品,最后按预算定维度与量化。把顺序做反(先选产品再想规模)是选型失败的主因。

常见坑清单

  • 用后置过滤做多租户:小占比租户检索结果为空,属于正确性事故。必须用前置过滤或按租户分区。
  • HNSW 删除后不重建:墓碑堆积导致内存不释放、召回率持续下降。墓碑超 20% 必须触发重建。
  • efConstruction 设得太小:为了省建索引时间把 efConstruction 设成 40,图质量差,后续 efSearch 怎么调都补不回来。离线可设 256。
  • 维度不做降维:直接上 3072 维,内存是 1024 维的三倍而质量提升有限。优先试 Matryoshka 截断。
  • 查询与文档用了不同嵌入模型:向量空间不一致,相似度计算无意义。模型版本必须显式锁定。
  • 过滤字段没有 payload 索引:前置过滤退化成逐条比较,延迟成倍上升。高基数过滤字段必须建索引。
  • 把网络存储当 DiskANN 的盘:SSD 索引放在 EBS 上延迟从 30 毫秒涨到几百毫秒。DiskANN 必须用本地 NVMe。
  • top_k 设得过大直接喂 LLM:噪声淹没信号且 token 成本飙升。召回多、喂入少,中间用重排收口。
  • 忽略副本与重建的内存翻倍:只按单副本算容量,上线后扩容或重建时 OOM。
  • 迁移时全量重灌向量:忘了嵌入模型版本绑定,新旧向量混在同一个索引里,相似度失去可比性。
  • nlist 设成固定值不随规模调整:数据从百万涨到千万,簇数没变,每簇样本过多导致 nprobe 需要成倍加大。nlist 应随 sqrt(N) 增长。
  • 只压向量不压图边:以为 int8 量化能把内存降到四分之一,实际图边开销(M×2×4 字节)不随量化下降,大规模下它可能占内存的 10% 到 20%。

小结

向量检索的工程决策可以收敛成三个问题。第一,数据规模多大:十万以内用 Flat,百万级用 HNSW,千万级在 HNSW 与 IVF-PQ 间取舍,十亿级考虑 DiskANN。第二,内存预算是多少:不够就上 int8 量化(省 4 倍、损失约 1%)或 Matryoshka 降维(省 3 倍、损失约 1%)。第三,更新频率多高:高频增删选 IVF-PQ,低频用 HNSW 加增量索引与定期重建。

选型层面,跟随现有技术栈永远优于追逐性能数字:已有 Postgres 就用 pgvector,需要独立服务就选 Qdrant,十亿级分布式才考虑 Milvus。调优层面,把 efSearch 从 64 往上扫到召回率拐点,把 nprobe 从 nlist 的 1% 起步,这两个参数是延迟与召回之间最直接的旋钮。

最后一条经验:索引的职责是「不漏」,重排的职责是「准」。把召回集做大、把精排做准,比在单层索引里死磕参数更有效。当召回率不足时,先加 top_k 加重排,再考虑调索引参数。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「LLMOps」更多文章

  1. 语义缓存与 Prompt 缓存
  2. 结构化输出与函数调用
  3. 多智能体编排与工作流引擎