上篇Doris的文章里聊了它的物化视图,那么同样作为另一种用来加速表查询的功能——Bigmap,它在Doris中又是怎么玩的呢?
Doris对Bitmap的描述,个人认为比较零散,那里一小坨,这里一小撮的,从官方文档上来看,Bitmap的核心功能有2个:一个作为Doris表的一种特殊字段类型;另一个作为一种Doris特殊的索引。
虽然这两个功能的底层实现原理是有相同之处的,且都叫bitmap,但是官方文档对这两个不同功能阐述时,完全没有一点相关性描述,感觉这两者是完全割裂的,一丢丢关系都没有。
我不知道会有多少人在第一次看到官网描述时,一个章节说bitmap是一个字段类型,另一个章节又突然说bitmap是一个可以为字段添加的索引,会不会一脸懵逼,反正我当时懵逼了。

数据类型章节对Bitmap的描述

索引章节对Bitmap的描述
我们知道,Bitmap是一种相对比较特殊的数据结构,它以占用存储空间小(一个数据对象只占用1个bit)而出圈,通常有两大核心功能为:
1. 用来实现对数据的去重统计;
2. 判断目标数据是否存在;
鉴于它的这一优势,很多数据库也都引入了这个功能。
那么Doris是如何利用这个特殊的数据结构,去发挥在字段类型和索引上的优势的呢,又是否真的好用呢?
PS:我当前Doris版本为:1.2.3
0. 先说说Doris的索引种类
在说Bitmap索引之前呢,咱先来说说Doris的索引种类,跟其他一般数据库不太一样的是,Doris支持多种对数据的索引方式。
我们知道,索引本质上就是对数据进行排序,那么不同类型的索引,则代表了不同的排序规则。
Doris提供了多种索引方案,这些索引可以分为两大类:
一类为内置索引,使用者无法选择;
另一类为用户可供选择的索引;
对于内置索引而言:当我们创建一张Doris表,指定排序字段(聚合模型表也叫key字段)时,Doris就已经帮我们选择好了内置的索引方式。
对于用户可供选择的索引而言:需要我们在建表的时候额外指定,或者通过create index语句创建,目的是用来进一步加速数据查询,或者说是为了弥补内置索引的一些不足而提出来的,官网也把这种类型的索引叫做二级索引(个人觉得还是叫用户索引会更为贴切一点)。
那么今天要说的这个bitmap,就是通过create index来创建的。
1. bitmap索引的创建方式
Doris虽然提供了4种不同的用户索引,但是其中有2个要到2.0版本才能用。

而当前能通过用户创建的这两个索引,其创建方式还不一样,个人认为这一点对于使用者来说是不友好的。
比如BloomFilter的创建方式是通过在创建表时,在表属性中指定:

而bitmap索引的创建方式方式,则是通过明确指定索引的方式:

同样都是创建索引,但是创建方式的差别,多少还是会让使用者有些不适,如果Doris在后续版本中考虑统一就最好了。
2. bitmap的实现原理
既然是索引,那么它的核心功能自然就是为了加速查询,只不过相比普通索引而言,它的实现方式会显得有些不一样。
普通索引的原理一般就是拿原有索引字段的值,根据排序规则进行排序,而bitmap的玩法,显然要复杂一些,比如对于如下这个列来说:
| column_xxx |
| a |
| b |
| a |
| c |
| b |
| c |
如果对其创建bitmap索引的话,那么它在数据库中的存储方式就是这样的:

有序字典记录该索引列去重后,且排序后的数据,Bitmap中则记录每个列数据所在的行号。
这样做的最大好处在于,既可以通过等值匹配,又可以通过范围查找,快速筛选出目标列的数据。
3. bitmap的实践
理论再牛逼,咱也得通过实践来验证,看到底有没有那么说的那么厉害。
我这有一张通过kafka的routine load导数据过来的表,当前有8亿+条记录,应该比较合适做这个验证。

通过官网的描述,要想创建bitmap索引,对其字段类型是有一定要求的,比如它说不支持String类型(text类型)的字段:

bitmap支持的数据类型
3.1 查询速度不升反降
可神奇的是,我硬是在一个Text类型的字段上创建bitmap索引成功了(注:官方文档描述的版本跟我使用的Doris软件版本是一致的)。

再看我创建该字段的bitmap索引语句:

然后我查看后台的创建进度,跑的好好的,也没有报错,而且几分钟过后,就创建成功了。

那既然Text类型的字段能让我创建bitmap索引成功(难道官网描述不靠谱?),那咱就看一下这个添加了该索引的字段,到底能不能带来查询效率上的提升。
作为对比,我专门在对target_ip这个字段创建bitmap索引之前,已经做了一次没有索引情况下的查询,查询结果如下:

可以看到,花了2.79秒。
当我创建完bitmap索引之后,查询结果如下:

确保bitmap索引创建完毕

bitmap索引创建完毕后查询的结果
咋回事,怎么还变慢了呢?
关键我连续执行多次,查询时间依然是5秒左右,这过程中也没有新数据写入啊,你不能加快我的查询,总不能还变得更慢吧?
根据对bitmap数据结构的分析,我猜变得更慢的原因在于,因为要查询的数据量比较大,而且需要对这些数据进行count聚合,而这个聚合通过bitmap来进行,可能还没有原数据表聚合来的更快。
当然,这只是我的猜测。
是因为我这个target_ip列是高基列(不一样的值太多)吗?但是查询一下发现也不是啊:

这个基数,不正好符合网上说的bitmap适合非高基,但也不是特别低基的列吗?
难道真是因为Text这个字段类型,而导致的无法加速查询吗(后来证明不是的)?还是其他什么不知名原因?
如果是的话,那为啥让我能够在这个字段上创建bitmap索引成功呢?不能理解。
3.2 合适的条件才能加速
既然怀疑可能是因为字段类型的原因,那咱就把这个字段类型给改为满足文档要求的varchar类型试试,结果我通过alter语句一改,发现自己又天真了:

人家压根就不让我改,种种迹象表明,这个String类型(或者Text类型)在Doris好像就是个下等公民,之前我用它作为key的排序字段也不被允许,看来以后得少用这种类型。
既然不能让我修改字段类型,且在其他字段上测试,效果也不明显(最好找非key字段),那我就只能新建表了。
于是我就新建另一种表,同时把target_ip这个字段类型给改为varchar。

同时,我又得重新灌数据,因为要灌的数据量非常大(亿级规模才有意义),试了几种导入方式,最终还是通过kafka的 routine load靠谱。
一番折腾之后,我又往这张新表灌进了5.5亿+条数据。

然后,我对target_ip这个字段来一个跟之前一样的筛选查询(此时还没有创建bitmap索引):

可以看到,该查询用时1.26秒,为了避免一次查询反映的时间可能不准,同样的语句我执行了多次,都是这个时间范围。
那么接下来,就对这个已经是varchar字段的target_ip创建bitmap索引了:

查看后台运行状态:

一会运行完毕之后,就可以查看到该表创建成功的索引:

然后,我们再看,创建完bitmap的target_ip字段,其查询效率是什么样的:

可以看到,这个速度比没有创建bitmap之前,确实快了不止一个数量级。
那为什么这次可以这么快呢,我估计是因为这个查询到的数量问题。
4. 总结一下
从以上实践可以看出来,bitmap在某些场景下确实可以提高查询效率,比如在查询到的数据量很小的情况下,它的加速性能明显。
而在某些情况下却不能,不但不能,反而效率还会下降,比如当要查询的目标结果数据量比较大时,创建bitmap索引,不但不会变快,反而效率会变低。
所以,再次证明那句话,任何号称能提高查询效率的方案,都是有其适用前提的,不要去迷信任何所谓的“黑科技”,实践才能出真知(网上一众都是各种吹牛逼的介绍,我也是没想到)。
最后,本来想把bitmap作为字段类型的功能也一并聊一下的,发现一不小心内容就写多了,那这部分咱们下一篇继续...
