向量索引的混合查询方法,你选对了吗?

作者:舸灏,OceanBase 高级技术专家
单一的向量近似最近邻查询,往往并不能满足实际业务的需求。用户通常都需要在向量检索中联合标量条件进行过滤,例如数据产生时间,知识库 id 等。还有一类需求是将全文或者多路向量索引查询的结果进行融合排序。本文解读 OceanBase 向量索引混合查询的原理和使用,以及产品规划。
一、标量与向量索引的混合查询
在正式介绍标量和向量索引的混合查询前,我们先举一个场景示例。例如:查询评分 4.5 以上,深圳南山区,评价最好的,价格实惠的店铺。这个查询既包含标量条件评分 4.5 以上,又包含地理区域限制,还需要按照语义“评价最好的,价格实惠”进行向量的近似最近邻查询。
数据库表结构的定义关系为:
id :店铺 ID
score :评分
position:店铺所在地理位置坐标
comment_vector:用户评价对应的向量 在向量列上创建向量索引、Geometry 列上创建空间索引。
对应的查询 SQL 如下:

在进行这类混合查询时,带上标量条件的方法和普通 SQL 没有任何区别,即直接将查询条件放到 where 后即可。但使用向量索引是有语法要求的,需要 order by,然后使用向量索引对应的 distance 表达式,并在其后加上 approximate 关键词。如果不加上 approximate 关键词,则会进行全表扫描进行精确查询,而不会使用向量索引。
在上文 SQL 中我们加上了 Hint(即 /*+index(t1 idxg) */)指定了向量检索时使用空间索引,此处就引出了第一种带标量过滤的查询方式,即 Pre-filtering(前过滤)。
在实际使用时,如果没有特殊要求,不需要指定 hint,OceanBase 会依据统计信息自动选择最合适的标量索引和查询方式。
查询方式 1:Pre-filtering(前过滤)
Pre-filtering 是指先进行标量过滤的一种查询方式。
例如在上文的例子中,我们通过 hint 指定在进行向量检索前,先查询空间索引,获得所有满足 st_intersects 条件的数据。每一个向量都对应一个唯一 ID 作为其标识,这些唯一 ID 会被用来构造 bitmap。bitmap 则会作为向量检索的上下文。在向量检索的过程中,每找到一个候选向量,就会校验其 ID 是否在 bitmap 中,如果不在,则说明其不满足标量过滤条件,需要继续查找下一个候选向量,直到找到 limit K 个结果。

Pre-filtering 的优点是,在 Filter rate 高时,会通过标量索引过滤掉大部分数据,减少后续的向量计算开销。然而,当标量过滤性不太好时候,Pre-filtering 就不是最合适的过滤方式了。例如 100 万数据,过滤条件只能筛掉其中的 50%,那么扫描索引表以及构造 bitmap 的代价仍然是非常大的。当然在一些特定场景,比如过滤条件是简单的 range,会有一些优化。另外,如果过滤条件非常复杂,又没有标量索引可用的情况下,在做 Pre-filtering 进行标量过滤,就相当于对主表进行全表扫描,对每行数据还要执行过滤条件表达式计算,会导致性能不佳。
查询方式 2:In-filtering with extra info(中过滤)
为了解决 bitmap 构造代价大的问题,OceanBase 引入了 In-filtering,即在向量索引过程中校验标量条件。这种方法需要将过滤字段加入到向量索引中,即作为每个向量的额外信息(extra info)。

In-filtering with extra info 的优点是,不用先构造 bitmap,所以没有额外的 I/O 开销,适用于 Filter rate 中高的场景。但这种方式会占用额外内存,使用成本较高,也要求过滤条件相对简单。
查询方式 3:Iterative-Ann(迭代式)
为了应对前面两种方案不适用的情况,OceanBase 还实现了一种迭代式过滤的查询方法:Iterative-Ann,这种方法会使用多次迭代来完成查询,每一次迭代会返回向量距离最近的一批数据,然后再进行标量条件的校验,过滤掉不满足条件的数据。之后依据缺少的数据量,估算下一次迭代需要返回的向量近似最近邻结果的数量,调整参数再进行一轮查询。知道返回 limit K 条结果。 为此我们改造了传统的后过滤实现,在向量索引的查询中记录上下文,在前次迭代后,如果发现满足过滤个条件的数据量不足,就可以通过上下文继续查找,把下一批的数据迭代出来。
这种查询方式适用于 Filter rate 中低,过滤条件复杂的场景,在这类场景下,Iterative-Ann 的计算量反而最少,性能会更好,也不会出现传统后过滤方法少数据的问题。

Iterative-Ann 不适合 Filter rate 高的场景。在选择性非常好的情况下,比如 100 万数据中可能只有 100 条数据是满足过滤条件的,此时使用 Iterative-Ann,由于向量索引只关注向量距离的相似性,可能需要迭代很多轮,甚至计算完大部分向量后才能找到满足的过滤条件。
如何选择合适的查询方法?
上述三种查询方法分别适合不同的使用场景,那么应该如何选择合适的过滤方法呢?
在 OceanBase 早期版本中,只能通过加 Hint 进行方法选择。OceanBase V4.3.5_bp2 实现了比较完备的基于代价估计的路径选择。下图是 Oceanbase V4.3.5_bp2 和 Milvus 2.5.11 的 Vectordbbench 性能测试对比。测试在 Cohere 768D 100 万数据集、top 100、recall 98%时,通过自动选择计划,在不同过滤条件下的性能。



上图中纵坐标分别是 QPS 或 RECALL (召回率),横坐标是过滤率,0 表示过滤率为 0%(无过滤条件)、99 则表示过滤掉 99%的数据。可以看到,不管是性能还是召回率,OceanBase 表现更优。
同样,对比其他向量数据库,OceanBase 的表现也毫不逊色。下图是我们在 AWS 基于 16C64G 的服务器,使用开源向量数据库评分工具 Vectordbbench 对几个主流的开源向量数据库进行性能测试的结果。测试数据集分别为:768 维、100 万的数据集;1536 维、50 万的数据集。

图中的横轴代表召回率,纵轴代表 QPS,可以看到,在同等召回率下,OceanBase 的表现仍然出色,已经达到了开源向量数据库的领先水平。
OceanBase 具备计划缓存的能力,可以减少 SQL 硬解析的开销,复用之前生成的执行计划,但如果过滤条件的参数发生比较大的改变,会导致命中的计划不一定合适。如下图所示,假设 c1 列有索引,在 c1<10 时,满足过滤条件的数据量极少,会生成 pre-filter 计划。但当过滤参数变成 c1<100000 时,再命中前过滤计划就明显不合适了,此时由于过滤掉的数据很少,扫描索引表以及生成 bitmap 的代价会很大。

因此我们在 OceanBase V4.3.5_bp3 版本实现了执行期的自适应方法选择。例如上文原本计划前过滤,在条件发生改变后,会优先尝试前过滤,但在执行过程中如果发现耗时及预期行数已经超过前过滤的阈值时,OceanBase 会提前终止,并自动切换到迭代式过滤,从而保证性能相对好。同时我们也实现了执行期的统计,如果某个计划总是从前过滤切换到迭代式过滤,则会把计划的倾向性改为迭代式过滤,实现查询整体最优。
OceanBase V4.3.5_bp3 相较于 OceanBase V4.3.5_bp2 拥有更强的性能,如下图所示,我们在本地环境针对二者进行性能对比:

OceanBase V4.3.5_bp3 不仅整体性能提高了 10%,且避免了在不同过滤率下需要清理 PlanCache 才能重新生成最优执行计划的问题。
二、全文与向量的混合查询
全文索引和向量索引
全文索引和向量索引所擅长的场景并不相同,两者之间不是替代关系。
以查询“广州及周边一日游,适合拍照打卡但人少”的索引条件为例。全文索引能保证匹配到的结果带有指定的分词,例如“一日游”。但对于“适合拍照打卡但人少”的条件,由于查询语句中没有包含“无商业开发”等内容,全文索引就无法匹配到含义相近、但文字完全不同的内容,因此对“适合拍照打卡但人少”的查询结果不够精准。而向量索引可以基于“适合拍照打卡但人少”的含义匹配到“无商业开发”、“原始风貌”等内容,但因为向量不一定能精确表达数字,所以无法保证查询结果只包含“一日游”。在有某些专业术语的场景下也是类似。
因此产生了全文和向量索引结合使用的需求。全文和向量混合查询的一个问题在于,如何进行融合排序,向量索引每一种距离算法的值域范围都不相同,例如 Cosine 距离的值域范围是[0, 2],IP 和 L2 则可能会是一个比较大的数,而全文索引 BM25 的相关性得分范围也是不确定的。
不同索引综合排序方法 (rerank)
OceanBase 计划提供三种排序方法,倒数排序融合(Reciprocal rank fusion),加权求和(Weighted sum),还有基于 Rerank 模型的排序。这三种排序方法分别适用于不同场景。
倒数排序融合(Reciprocal rank fusion)
如果用户没有相关先验知识,可以直接使用倒数排序方法。倒数排序方法只根据结果集中的排名对其进行评分,不需要做分数归一化。当然这种排序方法也可以指定各路检索的权重。

例如:
结果 A: 词项匹配排名:1 → 得分 = 1/(1+60)≈0.0164 语义搜索排名:3 → 得分 = 1/(3+60)≈0.01593 总得分 = 0.0164+0.0159=0.0323
结果 B: ...
加权求和(Weighted sum)
加权求和需要将不同索引查询的结果分别归一化,再进行加权计算。需要用户对数据有一定的先验知识才能设定合适的权重。

例如向量权重 0.7, 全文权重 0.3 使用 Rerank 模型 使用 Rerank 模型,是目前排序效果最好的方法。但由于模型调用比较慢,代价比较大,适用于小 Top K 的场景,不适用于搜索结果集比较大的场景。
如何使用混合查询
在 OceanBase 当前版本中使用混合查询的方式如下所示,需要将每一路全文或向量作为一个子查询,然后将分数进行加权计算,或倒数排序计算。
OceanBase V4.4.1 计划提供功能更完善,更易用的融合查询接口,不需要用户手动来写复杂的子查询语法或进行分数计算。


产品未来规划
未来,我们将持续优化标量/全文/多向量混合查询的功能、易用性和性能。
全文和多向量混合查询。
功能完善:优先支持 RAG 场景,对标 ES 的混合检索能力。
易用性:提供新的更易用的混合查询语法。
性能优化:全文,稀疏向量查询的性能优化。
标量和向量的混合查询。
功能完善:支持全文索引或者多标量索引作为 pre-filtering 条件。
易用性:支持相似度阈值查询,不再需要用向量距离进行换算。
性能优化:进一步优化带有标量条件的向量索引查询性能
对于 OceanBase 的向量能力,大家有什么疑问和反馈,以及希望实现哪些能力,欢迎在评论区留言。
「老纪的技术唠嗑局」不仅希望能持续给大家带来有价值的技术分享,也希望能和大家一起为开源社区贡献一份力量。如果你对 OceanBase 开源社区认可,点亮一颗小星星✨吧!你的每一个 Star,都是我们努力的动力。
评论