内容提要
本文介绍Postgres与pgvector实现混合搜索(向量相似度+标量过滤)的模式。核心问题是向量索引返回近似排序结果,无法直接与WHERE过滤结合。pgvector 0.8提供迭代索引扫描,但需权衡召回率与性能。其他方案包括:部分HNSW索引(适合低基数稳定过滤)、过采样后过滤(适合高基数)、缓存响应(适合重复查询)。文章还提供EXPLAIN诊断方法和选择指南。
延伸解读
为什么不能直接合并索引
文章指出,B-tree索引返回的是满足条件的集合,而HNSW等向量索引返回的是近似排序的top-k列表,两者本质不同,因此无法像普通索引那样通过BitmapAnd合并。理解这一点有助于避免对Postgres规划器产生不切实际的期望,也是后续选择混合搜索方案的基础。
迭代扫描的代价
pgvector 0.8的迭代索引扫描虽然解决了过滤后结果不足的问题,但并非免费。低选择性的过滤条件会导致扫描范围大幅扩大,且受max_scan_tuples限制,可能返回少于LIMIT的结果。此外,relaxed_order与strict_order模式需要在排序准确性和性能之间权衡,实际使用时需根据业务对召回率和延迟的要求进行调优。
过采样池大小的估算
文章提供了一个估算过采样池大小的公式:pool_size = k / filter_selectivity × 2,并建议利用pg_stats中的统计信息动态计算。这有助于避免盲目设置过采样倍数,但需注意该公式只是起点,实际效果受数据分布和近似搜索误差影响,应通过测量调整。
缓存键必须包含过滤条件
在缓存混合搜索结果时,缓存键必须包含所有过滤条件(如类别、租户、日期范围等),否则可能将未过滤的结果错误地返回给带过滤的查询,重蹈“先向量后过滤”的召回率问题。文章强调,缓存不能修复糟糕的查询计划,应先确保过滤查询的召回率正确,再考虑缓存。
Q&A
什么是Postgres与pgvector的混合搜索?
混合搜索是指将向量相似度排序与标量过滤(如WHERE条件)结合起来的查询方式。例如,查找法律类别中最近30天发布的10篇最相似文档。
为什么不能直接对向量索引和B-tree索引进行交集操作?
因为B-tree索引返回的是满足条件的行ID集合,而HNSW或IVFFlat索引返回的是近似排序的top-k列表,不是集合。两者无法直接进行交集操作,因为向量索引不包含WHERE条件信息,返回的top-k可能不满足过滤条件。
pgvector 0.8的迭代索引扫描是什么?它有什么权衡?
迭代索引扫描是pgvector 0.8引入的功能,它允许在HNSW或IVFFlat索引扫描时持续遍历,直到找到足够满足WHERE条件的行,或达到安全限制。权衡包括:低选择性过滤会导致扫描更多图节点,max_scan_tuples可能限制结果数量,strict_order保证顺序但性能较差,relaxed_order允许轻微乱序以提升性能,以及内存使用可能增加。
什么是部分HNSW索引?它适用于什么场景?
部分HNSW索引是只包含满足特定过滤条件的行的HNSW索引,例如只对category='legal'的行建立索引。它适用于过滤条件低基数且稳定的场景,如少数几个类别或租户。优点是索引更小、构建更快,且在该子集内召回率接近100%。缺点是每个过滤值都需要一个索引,不适合高基数或开放式的过滤条件。
如何通过过采样和过滤来实现混合搜索?
过采样方法是从全表向量索引中获取比实际需要更多的候选结果,然后在SQL中应用过滤条件并重新排序。例如,先取200个最近邻,再过滤出category='legal'的行,最后取前10。过采样倍数需要根据过滤选择性调整,公式为pool_size = k / filter_selectivity × 2。同时需要提高hnsw.ef_search以确保索引能返回足够的候选。
如何诊断混合搜索查询是否使用了正确的索引?
使用EXPLAIN (ANALYZE, BUFFERS)查看执行计划。如果看到Index Scan using docs_hnsw,表示使用了向量索引;如果看到Index Scan using docs_legal_hnsw,表示使用了部分索引;如果看到Bitmap Heap Scan,表示先使用了标量索引;如果看到Seq Scan,表示全表扫描。如果预期使用向量索引但看到Bitmap Heap Scan,说明规划器认为标量过滤更高效。
在什么情况下应该选择缓存响应作为混合搜索的解决方案?
当相同的混合查询被频繁重复执行时,例如相似产品推荐、相似文章推荐等,可以考虑缓存响应。缓存可以预计算(存储时缓存)或查询后缓存。缓存键应包含查询向量、过滤条件和限制数,并需要处理失效(如嵌入变化或过滤条件变化)。缓存不能解决根本的召回问题,应先确保查询本身正确。