FeatureBase 将整型数值存储在二进制, 范围编码(Range-Encoded), 位切片索引(Bit-sliced Indexes)中,这篇博客分析了全部的实现。
引言
FeatureBase 是围绕着将所有内容表示为 bitmap 的概念构建的。对象之间的联系,例如,被视为 bool 值-关系存在或不存在-这些 bool 值的集合被存储为一系列 0 和 1。这些 bool 集合是 bitmaps,通过不同的按位运算使用 bitmaps,我们能非常快速的执行复杂查询。此外,bitmap 压缩技术使我们能够以非常紧凑的格式表示大量数据,从而减少了存储数据和执行查询所需的资源量。
使用 bitmaps 和Roaring bitmap compression, FeatureBase 非常擅长对数十亿个对象执行分割查询。但我们经常看到使用整数值会很有用的用例。例如,我们可能希望执行一个排除 foo 值大于 1000 的所有记录的查询。这篇文章逐步解释了我们如何将范围编码 bitmap 添加到 FeatureBase 中,使我们能够在查询操作中支持整数值。
这篇文章中描述的所有概念都是基于一些非常聪明的人在过去几十年中所做的研究。这是我试图在更高的层面来描述事情,但我鼓励你更多地阅读Bit-sliced Indexes和Range-Encoding。
Bitmap 编码
首先,让我们假设我们想对动物王国的每个成员进行编目以这样一种方式,我们可以轻松有效地根据他们的特征来探索各种各样的各种物种。因为我们谈论的是 bitmap,所以示例数据集可能如下所示:
上面的例子显示了一组相等编码的位图,其中每行每个特征都是一个位图,指示哪些动物具有该特征。尽管这是一个相当简单的概念,但相等编码可能非常强大。因为它允许我们将一切表示为 bool 关系(即海牛有翅膀:是/否),所以我们可以对数据执行各种按位操作。
下一张图显示了我们如何通过对无脊椎动物和呼吸空气位图执行逻辑 AND 来找到所有呼吸空气的无脊椎动物。根据我们的样本数据,我们可以看到香蕉蛞蝓、花园蜗牛和车轮虫都具有这两种特征。
鉴于我们对相等编码位图的了解,我们可以采取几种(公认的幼稚)方法。一种方法是为每个可能的 Captivity 值创建一个特征(位图),如下所示:
另一种方法是创建 Captivity 范围的存储桶,而不是将每个可能的值表示为唯一的位图。在这种情况下,您可能会遇到这样的情况:
这两种方法中的任何一种都是某些问题的完全有效的解决方案,但对于基数极高且信息丢失是不可接受的情况,我们需要另一种方法来表示非布尔值。我们需要这样做,即我们可以对值的范围执行查询,而无需编写真正庞大而繁琐的 OR 运算。为此,让我们讨论一下范围编码位图,以及它们如何避免我们在以前的方法中遇到的一些问题。
范围编码位图
首先,让我们以上面的例子为例,看看如果使用范围编码位图会是什么样子。
这种编码方法使我们能够执行以前所做的范围查询,但我们可以从一个或两个位图中获得所需的内容,而不是对许多不同的位图执行 OR 运算。例如,如果我们想知道哪些动物圈养的标本少于 15 个,我们只需提取 14 位图就可以了。如果我们想知道哪些动物的圈养标本超过 15 个,这有点复杂,但并不多。为此,我们提取表示最大计数的位图(在我们的情况下是 956 位图),然后减去 15 位图。
这些操作比我们以前的方法简单得多,效率也高得多。我们已经解决了为了找到我们的范围而将数十个位图进行 OR 运算的问题,并且我们不会像在桶方法中那样丢失任何信息。但我们仍然有几个问题使这种方法不太理想。首先,我们仍然需要保留一个位图来表示每一个特定的圈养数量。除此之外,我们还增加了复杂性和开销,不仅要为我们感兴趣的值设置一点,还要为每一个大于该值的值设置。这很可能会在大量写入的用例中引入性能问题。
理想情况下,我们想要的是具有范围编码位图的功能和相等编码的效率。接下来,我们将讨论位切片索引,看看它如何帮助我们实现我们想要的。
位切片索引
如果我们想使用范围编码位图来表示从 0 到 956 的每一个可能值,我们必须使用 957 位图。虽然这可以工作,但这并不是最有效的方法,而且当可能值的基数非常高时,我们需要维护的位图数量可能会变得过高。位切片索引使我们能够以更有效的方式表示这些相同的值。
让我们看看我们的示例数据,并讨论如何使用位切片索引来表示它。
所以这很好,但我们基本上刚刚找到了一种更有效的相等编码策略。让我们看看当我们将位切片索引与范围编码相结合时会是什么样子。
范围编码位切片索引
二进制的组件
如果我们使用二进制的组件而不是将 Captivity 值表示为十进制的组件,那么我们最终得到的是一组范围编码的位切片索引,如下所示:
但请记住,就像我们之前在十进制表示的位图 9 中看到的那样,位图 1 总是一个,所以我们不需要存储它。这就给我们留下了这样的方式:
FeatureBase 中的范围编码位图
通过在 FeatureBase 中实现范围编码位图,用户现在可以存储涉及数十亿个对象的整数值,并可以非常快速地执行按范围进行过滤的查询。我们还支持像 Sum()这样的聚合查询。圈养的有翼脊椎动物的总数是多少?没问题。
作为最后一个练习,让我们演示如何在 FeatureBase 中存储和查询示例圈养数据。
# 创建一个名为“animals”的索引。curl -X POST localhost:10101/index/animals
# 创建一个“特征(traits)”视图来存放圈养(captivity)值。curl localhost:10101/index/animals/frame/traits \ -X POST \ -d '{"options":{"rangeEnabled": true, "fields": [{"name": "captivity", "type": "int", "min": 0, "max": 956}] } }'
# 将圈养值添加到字段中。curl localhost:10101/index/animals/query \ -X POST \ -d 'SetFieldValue(frame=traits, col=1, captivity=3) SetFieldValue(frame=traits, col=2, captivity=392) SetFieldValue(frame=traits, col=3, captivity=47) SetFieldValue(frame=traits, col=4, captivity=956) SetFieldValue(frame=traits, col=5, captivity=219) SetFieldValue(frame=traits, col=6, captivity=14) SetFieldValue(frame=traits, col=7, captivity=47) SetFieldValue(frame=traits, col=8, captivity=504) SetFieldValue(frame=traits, col=9, captivity=21) SetFieldValue(frame=traits, col=10, captivity=0) SetFieldValue(frame=traits, col=11, captivity=123) SetFieldValue(frame=traits, col=12, captivity=318) '
# 查询所有圈养值超过 100 个的动物。curl localhost:10101/index/animals/query \ -X POST \ -d 'Range(frame=traits, captivity > 100)'
# 查询圈养动物总数。curl localhost:10101/index/animals/query \ -X POST \ -d 'Sum(frame=traits, field=captivity)'
结论
我在这篇文章中描述的例子展示了我们如何使用位切片索引来显著减少表示范围整数值所需的 bitmap 数量。通过对索引应用范围编码,我们能够对数据执行各种范围查询。下面的图表比较了我们讨论的不同方法。
想要了解Go更多内容,欢迎扫描下方👇关注公众号,回复关键词 [实战群] ,就有机会进群和我们进行交流
分享、在看与点赞Go