Postgres 向量检索索引怎么选,IVFFlat 与 HNSW 的代价差在哪

在 Postgres 里做向量检索,索引选型比模型选型更早决定系统的天花板。pgvector 提供 IVFFlat 与 HNSW 两种近似最近邻索引,前者建得快、占内存少,召回率靠探测数补,后者查询更快,建索引更慢也更吃内存。线上抖动常常出在另一处,过滤条件和向量检索叠加之后召回被削掉。本文面向正在评估向量检索选型的后端与搜索工程师。

向量检索为什么要放进数据库里做

把 embedding 和业务数据放在同一个事务里,能省掉一整套同步流程,过滤、权限、分页、join 都能复用已有的 SQL 和索引。

代价集中在三处。向量列体积大,一次 UPDATE 会重写整行。索引构建要吃 maintenance_work_mem。写入持续时,autovacuum 的压力比普通表更明显。

IVFFlat 与 HNSW 的构建代价和查询代价差在哪

IVFFlat 把向量划进若干列表,查询时只扫离查询向量最近的几个列表。它需要先有数据再建索引,列表中心靠 k-means 训练出来,空表建出来的索引召回很差。列表数可以按行数估,小表用行数除以一千,大表用行数的平方根。

HNSW 建的是多层图,没有训练步骤,空表也能建。m 决定每层最多保留多少条连接,默认 16,ef_construction 决定建图时的候选列表大小,默认 64。调大 ef_construction 能提高召回,建索引时间和写入速度一起变差。图装不进 maintenance_work_mem,构建时间会明显拉长。

四个参数各自改变什么

  • lists 决定把数据切成多少个簇。太少则单个列表太长,太多则中心点稀疏、召回下降。
  • probes 调大提高召回、降低速度,调到等于 lists 时结果精确,规划器可能判定全表扫描更便宜而放弃索引。
  • 调大 m 提高召回,同时增加内存占用与建索引时间。
  • ef_construction 影响建图质量,ef_search 影响查询时候选队列的长度。前者一次性付出,后者每次查询付出。

probes 与 ef_search 可以按查询调,lists、m 与 ef_construction 写进索引定义,改一次就得重建。

过滤条件叠上向量检索,准确率为什么掉

近似索引先扫描、后过滤。条件只命中一小部分行时,引擎先按 ef_search 扫出候选再筛,剩余行数被过滤比例直接乘掉。默认 ef_search 是 40,条件命中一成行,平均只剩四条,连 LIMIT 5 都凑不满。

  • 在过滤列上单独建 B-tree 或多列索引,条件选择性很高时让精确索引先收窄范围。
  • 打开迭代扫描,结果不够时继续往下扫。严格序保证按距离排序,放宽序召回更好但顺序会轻微错位。
  • 过滤值很多时按过滤列做列表分区,避免不同租户的向量互相影响召回。

迭代扫描要设上限,HNSW 默认最多扫两万条,IVFFlat 用 ivfflat.max_probes 限制。上限放得太宽,慢查询会拖住连接池。

什么时候该换成专用向量数据库

数据到几亿条、维度高、写入持续且延迟要求严,或者需要多租户隔离时,专用向量数据库的分片与磁盘优先索引更合适。

  • 召回率调到可接受之后,P99 延迟是否还在预算内。
  • 索引重建窗口能否塞进维护时段。
  • 过滤条件是否已经成了召回率的主要瓶颈。

有一条长期不满足,就先做对比测试再决定迁移,代价集中在双写一致性、回滚方案和运维复杂度。

pgvector 之外的实现路径

Postgres 侧还有别的索引实现可选,例如基于 DiskANN 的磁盘优先索引,以及把量化与分层路由结合的 IVF 变体。共同思路是把全精度向量留在堆表里,索引只存压缩编码,靠误差上界排除掉不可能成为最近邻的候选,只对少量候选回表精排。这类设计在内存受限的场景更划算,代价是参数体系各不相同。

常见问题

召回率怎么量? 拿一批查询向量,先用不开索引的顺序扫描算出真实最近邻,再和走索引的结果比对命中比例。