首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >目前仅此一家支持:向量索引加装前置过滤器

目前仅此一家支持:向量索引加装前置过滤器

作者头像
用户4035096
发布2026-07-09 19:42:55
发布2026-07-09 19:42:55
40
举报

又涨知识了, 向量索引前置/预过滤器

在聊索引扫描的前置过滤器时, 先举个例子.

家里装过自来水过滤器的朋友一定知道, 过滤器分前置过滤器和后置过滤器, 前置过滤器一般装在入户的位置, 主要是过滤大颗粒杂质, 保障所有其他出水口不容易被堵塞. 后置过滤器主要用于过滤细粒度杂质, 例如厨房可能回安装直饮水过滤器.

如果把前置过滤器拿掉, 可能有好处, 也可能带来坏处, 和水质有关.

水质差, 大颗粒多, 拿掉肯定不好.

水质好, 拿掉可能有好处. 过滤器毕竟是影响水流量量多, 拿掉前置过滤器水通量可能变大.

下面我们来看看索引扫描的前置过滤和后置过滤.

很多人只知道索引扫描的后置过滤, 那么我们就来看看什么是索引扫描的后置过滤.

这条SQL的索引是items_pkey, 索引扫描到符合条件的记录后, 回表, 判断tuple可见性, 并且对可见tuple获得embedding字段的值, 再输入到后置过滤器得到满足vector_norm(embedding) < 0.5的tuple.

代码语言:javascript
复制
EXPLAIN (COSTS FALSE)  
SELECT * FROM items WHERE id <= 5000 AND vector_norm(embedding) < 0.5;  
                          QUERY PLAN                            
--------------------------------------------------------------  
 Index Scan using items_pkey on items  
   Index Cond: (id <= 5000)  
   Filter: (vector_norm(embedding) < '0.5'::double precision)  

什么是索引前置过滤呢? 只有索引扫描过程中需要多步计算时可能会用到前置过滤. 例如为了减少内存使用、加速裁剪等, 采用了rabitq量化和残差量化(rvq)分层结构的向量索引vectorchord, 这类索引扫描通常分多个步骤, 初筛、剪枝/精排等最后输出最终结果.

剪枝/精排可能涉及较大的向量计算运算量, 如果向量结果中很多tuple是不符合非索引条件(其他where条件)的tuple, 那么就可以考虑使用索引扫描的前置过滤器. 通常可能会被称为lazy vector compute(就是真正要计算向量时才计算).

例如这条SQL

代码语言:javascript
复制
EXPLAIN (COSTS FALSE)   
SELECT * FROM items WHERE id % 97 = 0 ORDER BY embedding <-> '[0, 0, 0]' LIMIT 10;  
                     QUERY PLAN                        
-----------------------------------------------------  
 Limit  
   ->  Index Scan using items_embedding_idx on items  
         Order By: (embedding <-> '[0,0,0]'::vector)  
         Filter: ((id % '97'::bigint) = 0)  

索引扫描前置过滤器可能是这样的:

注意它依旧是索引扫描的行为, 并不是通过全表扫描去获取非向量字段的值.

为什么上面这条SQL要采用前置过滤器呢?

因为大量tuple不满足WHERE id % 97 = 0条件, 并且"索引计算(剪枝/精排)"这个步骤的运算量极大, 消耗大量cpu.

那么是不是所有的索引都支持前置过滤功能呢? 非也, 其实这个功能来自vectorchord插件.

下面的内容翻译自: https://docs.vectorchord.ai/vectorchord/usage/prefilter.html

prefilter (前置过滤器/预过滤器)

在索引扫描过滤中,索引首先执行扫描,然后 PostgreSQL 检查是否满足过滤条件。“过滤器”指的是 PostgreSQL 查询计划中索引扫描节点上的过滤器。这通常由SQL 语句中的WHERE子句构成。过滤器不需要具有特定的形式。

代码语言:javascript
复制
EXPLAIN (COSTS FALSE)  
SELECT * FROM items WHERE id <= 5000 AND vector_norm(embedding) < 0.5;  
                          QUERY PLAN                            
--------------------------------------------------------------  
 Index Scan using items_pkey on items  
   Index Cond: (id <= 5000)  
   Filter: (vector_norm(embedding) < '0.5'::double precision)  

然而,对向量搜索来说, 这并不总是最有效的方法。考虑以下查询。

代码语言:javascript
复制
EXPLAIN (COSTS FALSE)   
SELECT * FROM items WHERE id % 97 = 0 ORDER BY embedding <-> '[0, 0, 0]' LIMIT 10;  
                     QUERY PLAN                        
-----------------------------------------------------  
 Limit  
   ->  Index Scan using items_embedding_idx on items  
         Order By: (embedding <-> '[0,0,0]'::vector)  
         Filter: ((id % '97'::bigint) = 0)  

假设ID分布均匀, 由于id % 97 = 0这个条件的存在, 检索10条满足过滤条件的向量,索引扫描大约要放大97倍, 也就是索引需要扫描970行。这表明索引执行了大量的冗余计算。如果索引可以利用过滤条件进行剪枝,计算量将显著减少。向量索引提供了一个 GUC 参数vchordrq.prefilter,允许根据过滤条件对搜索空间进行剪枝。

代码语言:javascript
复制
SET vchordrq.prefilter = on;  

预过滤使向量索引能够根据过滤器执行搜索。这会精简搜索空间,而较小的搜索空间可以提高搜索效率。然而,检查过滤条件是否满足也会带来开销。因此,仅当过滤条件严格(剔除大量行)且计算成本低廉(计算成本远低于计算向量距离)时才建议使用预过滤。为了帮助理解,我们提供了两个错误的使用示例:

id % 97 > 0:这个过滤器不太耗cpu, 但是大多数是满足条件的. 用在前置过滤是不可取的. email ~ '^([a-zA-Z]+)*$':这个过滤器是正则匹配, 可能比较耗费cpu(但是相比向量计算的cpu也许还好), 但是满足条件的tuple非常少. 所以用于前置过滤是可取的.

适合前置过滤的是:

  • 满足前置过滤条件的tuple很少
  • 前置过滤where条件对应的运算量不高(相比向量计算)

根据我们的实验结果,不同选择性下的 QPS 加速如下:

  • 选择性 1% 时加速 200%
  • 选择性 10% 时加速 5%

ps: vchordg不支持此功能。仅vchordrq支持预过滤.

参数

代码语言:javascript
复制
vchordrq.prefilter   

vchordrq.prefilter GUC 参数允许在计算距离之前先进行条件评估。例如,在查询SELECT * FROM items WHERE id % 2 = 0 ORDER BY embedding <-> '[3,1,2]' LIMIT 5中,索引通常会先计算所有有用的embedding <-> '[3,1,2]'向量距离,然后将行传递给 PostgreSQL,后者会过滤掉符合条件id % 2 != 0的行。此参数允许索引在计算距离之前预先评估条件并丢弃不匹配的行,从而提高查询效率。

更多改进思考

前置过滤节省了向量计算的CPU开销, 确没有节省其他条件计算带来的cpu开销和回表带来的IO.

其实还可以继续优化.

如果非索引条件是必选条件, 可能存在的更好方法是

  • 分区表, 例如前面提到的ID过滤, 可以使用分区表, 在符合条件的分区中进行向量搜索, 效率更高.
  • partial index, 与分区表类似, 通常存在于查询某些特定状态的记录(例如状态字段为激活和非激活, 属于2种经常查询的条件.)
  • include index, 把非向量索引条件的字段, 放到向量索引叶子节点, 可减少回表IO. 例如轨迹查询, TID和时间字段通常是索引条件, 其他字段是被查询字段, 采用index only scan和include index可大幅度减少回表IO.

参考

https://deepwiki.com/search/set-vchordrqprefilter-on-true_8b1a7b21-b2b0-41b8-b7e2-0e4f9eabc495

https://docs.vectorchord.ai/vectorchord/usage/prefilter.html

《重新发现PostgreSQL之美 - 8 轨迹业务IO杀手克星index include(覆盖索引)》

《PostgreSQL index include - 类聚簇表与应用(append only, IoT时空轨迹, 离散多行扫描与返回)》

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-11-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 digoal德哥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 又涨知识了, 向量索引前置/预过滤器
  • prefilter (前置过滤器/预过滤器)
    • 更多改进思考
    • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档