Bitset如何实现矢量搜索的多功能性

225 阅读6分钟

随着Milvus 2.0的发布,向量数据库的各种新的基本功能被一起提供。在这些新功能中,时间旅行、属性过滤和删除操作是相关的,因为这三个功能是由一个共同的机制实现的:比特集。

因此,这篇文章旨在澄清Milvus中bitset的概念,并通过三个例子解释它是如何支持删除操作、时间旅行和属性过滤的。

什么是比特组?

比特集是一个位数数组("0 "和 "1"),可以用来表示某些数据信息。使用比特集,你可以紧凑有效地存储某些类型的数据,而不是用Ints、floats或chars来存储它们。位组的工作原理是布尔逻辑,根据布尔逻辑,输出的值要么有效,要么无效,通常分别用 "1 "和 "0 "表示。"1 "代表有效,而 "0 "代表无效。由于比特集的效率很高,可以节省存储空间,因此也可以用来实现许多功能,如属性过滤、删除操作、时间旅行等。

从0.7.0版本开始,Milvus中引入了bitset的概念以实现删除功能。更具体地说,比特组被用来标记段中的每一行是否被删除。被删除的实体在相应的比特组中被标记为 "1",因此,被删除的实体在搜索或查询中不会被计算出来。

在Milvus 2.0版本中,bitset的应用被扩展以实现更多的功能,如属性过滤和时间旅行。比特集的一般原则保持不变。也就是说,如果一个实体在相应的比特组中被标记为 "1",那么该实体在搜索或查询时将被忽略。在Milvus中,比特组被用来启用3个功能。

  • 属性过滤
  • 数据删除
  • 带有时间旅行的查询

Bitset在Milvus中是如何工作的?

下面的例子用来说明比特组在Milvus中如何工作。

前提条件

假设有一个段有8个实体和一系列的数据操作语言(DML)事件,这些事件按照下图所示的顺序发生。

  • 其中四个实体,其primary_keys ,分别为[1, 2, 3, 4],当时间戳ts ,等于100时,就被插入。
  • 其余四个实体,其primary_keys 是[5, 6, 7, 8],在时间戳ts 等于200时被插入。
  • 当时间戳ts 等于300时,其primary_keys 为[7, 8]的实体被删除。
  • 只有那些primary_keys 是[1, 3, 5, 7]的实体才满足属性过滤的条件。

DML事件的顺序

案例一

假设一个用户为time_travel 设置的值是150。换句话说,当ts = 150时,用户对存储在Milvus的数据进行查询。比特集的生成过程如下图1所示。

在初始过滤阶段,filter_bitset 的结果应该是[1, 0, 1, 0, 1, 0, 1, 0],因为实体[1, 3, 5, 7]是有效的过滤结果,在比特集中被标记为 "1"。然而,当ts 等于150时,实体[4,5,6,7]甚至没有被插入到矢量数据库中。因此,无论过滤条件如何,这四个实体应该被标记为 "0"。现在的比特集结果应该是[1, 0, 1, 0, 0, 0, 0]。由于在Milvus中,比特集计算的一般原则是,在搜索或查询过程中,比特集中标记为 "1 "的实体会被忽略,所以在时间旅行和属性过滤之后的比特集结果需要被翻转,以便与删除的比特图相结合。filter_bitset 的翻转结果应该是[0, 1, 0, 1, 1, 1, 1]。

至于删除比特集del_bitset ,初始值应该是[0, 0, 0, 0, 0, 1, 1]。然而,实体7和8直到ts 是300才会被删除。因此,当ts 是150时,实体7和8仍然有效。因此,时间旅行后的del_bitset 值应该是[0, 0, 0, 0, 0, 0, 0]。

现在我们有两个经过时间旅行和属性过滤的比特集。 filter_bitset [0, 1, 0, 1, 1, 1, 1] 和del_bitset [0, 0, 0, 0, 0, 0, 0]。将这两个比特集与 "OR "二进制逻辑运算符结合起来。result_bitset 的最终值是[0, 1, 0, 1, 1, 1, 1]。也就是说,在接下来的搜索或查询阶段,只有实体1和3会被计算出来。

图1.带有时间旅行=150的搜索。

案例二

假设用户为time_travel ,设定的值是250。换句话说,当ts = 250时,用户对存储在Milvus的数据进行查询。比特集的生成过程如下图2所示。

和案例一一样,初始属性过滤阶段的结果filter_bitset 应该是[1, 0, 1, 0, 1, 0, 1, 0]。

ts= 250时,所有实体[1, 2, 3, 4, 5, 6, 7, 8]都被插入到矢量数据库中。因此,之前的filter_bitset 的结果仍然是一样的。同样,我们需要翻转filter_bitset 的结果,我们将得到[0, 1, 0, 1, 0, 1, 0, 1]。

至于删除比特集del_bitset ,初始值应该是[0, 0, 0, 0, 0, 1, 1]。然而,实体7和8直到ts 是300才被删除。因此,当ts 是250时,实体7和8仍然有效。因此,时间旅行后的del_bitset 值应该是[0, 0, 0, 0, 0, 0, 0]。

现在我们有两个经过时间旅行和属性过滤的比特集。 filter_bitset [0, 1, 0, 1, 0, 1, 0, 1]和del_bitset [0, 0, 0, 0, 0, 0, 0] 。将这两个比特集与 "OR "二进制逻辑运算符结合起来。result_bitset 的最终值是[0, 1, 0, 1, 0, 1, 0, 1]。也就是说,在接下来的搜索或查询阶段,只有实体[1,3,5,7]会被计算出来。

图2.带有时间旅行=250的搜索。

案例三

假设用户为time_travel ,设定的值是350。换句话说,当ts = 350时,用户对存储在Milvus的数据进行查询。比特集的生成过程如下图3所示。

我们看到与案例一和案例二相同:初始属性过滤阶段的结果filter_bitset 是[0, 1, 0, 1, 0, 1, 0, 1]。当ts= 350时,所有实体[1, 2, 3, 4, 5, 6, 7, 8]都被插入到矢量数据库中。因此,filter_bitset 的最终翻转结果是[0, 1, 0, 1, 0, 1, 0, 1],与案例二的情况相同。

至于删除比特集del_bitset ,由于实体7和8在ts=350时已经被删除,因此,del_bitset 的结果应该是[0, 0, 0, 0, 0, 1, 1]。

现在我们有两个经过时间旅行和属性过滤的比特集。 filter_bitset [0, 1, 0, 1, 0, 1, 0, 1] 和del_bitset [0, 0, 0, 0, 0, 1, 1]。将这两个比特集与 "OR "二进制逻辑运算符结合起来。result_bitset 的最终值是[0, 1, 0, 1, 0, 1, 1]。也就是说,在接下来的搜索或查询阶段,只有实体[1,3,5]会被计算出来。

图3.带有时间旅行=350的搜索。

下一步是什么?

在这个系列的博客中,我们之前介绍了Milvus 2.0中数据删除的设计。在本系列接下来的文章中,我们将介绍数据压缩的设计,以及Milvus 2.0的动态负载平衡。请大家继续关注。