在之前的clickhouse系列文章中,聊了它的物化视图功能,通过对该功能的实际体验,我觉得它对实际生产中能起到两大作用:
1. 能针对特定业务的查询(聚合)需求,在基础表(原始数据表)基础上,通过业务聚合(或筛选)逻辑,提供一种以存储空间换取查询效率的数据解决方案;
2. 可以充当外部数据源的导入功能,将CK支持的外部引擎表(integration 引擎)数据,根据条件(筛选或聚合都可以),接入到CK内部;
而对于Doris而言,从目前我了解的官方文档信息来看,只支持第1种,Doris的外部数据源导入,应该是不能通过物化视图的方式来实现(订:部分外部数据源可以,没CK支持的多),只能老老实实用专门的数据源导入方式进行。
作为Doris的一项重要功能,那么这篇文章,咱就来聊聊它的物化视图功能。
0. CK与Doris玩法的区别
对于CK来说:
物化视图是一个脱离了基础表(base表)之外的新表,我们在使用CK的物化视图功能时,是通过指定特定的物化视图名称来使用的。
对Doris来说:
则是把物化视图作为丰富、拓展、和加速基础表(base表)功能的一种高效的查询手段,是对基础表基于复杂查询和聚合查询性能的提升,且所有查询都是基于基础表进行,物化视图本身对查询者是无感知的。
1. 啥是rollup
从Doris官方描述来看,rollup这个功能有点类似于物化视图的低阶版本,或者说功能较少的初始版本,在0.12版本之前是这么玩的:

对于rollup,从官方给的各种描述和使用上来看,它的初衷应该是个特殊的索引,文档有的地方也把它叫做“物化索引”,难怪它的创建方式是用的是ALTER TABLE语句。
这玩意怎么用呢?从官方描述来看,核心有两大功能:
1.1 rollup的聚合功能
对于聚合模型表(Aggregate和Unique)来说,rollup可以用更少的key组合,来获取更粗粒度的进一步聚合统计。
具体怎么理解呢?
就是原本对于这个聚合模型表(Aggregate和Unique)来说,我们一般都是定义了一系列的组合key,比如(k1,k2,k3),然后数据写入这个聚合模型表中后,就会根据这3个key的顺序依次进行排序、分组,然后这3个key完全相同的,对他的value字段(假设value字段为v1)进行对应的聚合。
比如,我的这个例子:

aggregate模型表
这是基于用户上网ip、访问域名、上网时间进行的次数统计。
但是,如果我想要知道每个上网IP的上网总次数,对于这张表来说,就得通过如下聚合的方式:

创建rollup之前的聚合查询
这个时候呢,我们从这个查询语句的执行计划可以看出来,它是怎么执行的:

这个框框里面现在显示的是当前这个表的表名,意味着当前就是老老实实对现在的原始数据进行聚合后的结果,虽然能满足我们的聚合结果要求,但是我们知道,随着数据量的增加,这个聚合查询的效率一定会逐渐降低。
为了加速这个查询,于是这个时候,我们来创建个rollup试试看:

这个alter语句,就相当于给这张表根据我们的业务查询需要,添加了一个额外的聚合数据,但是这里面需要注意的是,这个rollup选择的字段中,必须是基础表字段的子集。
由于这个创建是异步的,所以它很快就会返回,但是由于这本质是启动了一个后台的数据聚合任务,因此一定是需要花时间去完成的,好在Doris提供了这个后台任务运行的状态查询方式:

由于这个表的数据量不大,因此这个rollup很快就完成了。
完成之后,我们再看这个聚合查询:

但可以看到,查询结果跟之前一样,而且查询时间更短了(虽然差别非常细微,因为这个表的数据量非常小)。
再瞅一眼查询计划:

这时候你发现,它已经利用到了rollup的数据了。
1.2 rollup的重新排序功能
rollup除了对Doris聚合模型表(Aggregate和Unique)有更进一步的再聚合功能外,它还可以对非聚合模型表(Duplicate表)有重新排序的功能。
怎么理解呢?
我们知道,对于Duplicate模型的表来说,原本是没有任何聚合功能的,既然基础表没有聚合功能,那么此刻它的rollup功能也就不具备这个功能(rollup能提供的功能必须依附于基础表之上)。
那Duplicate模型表具备什么功能呢?只有(重新)排序(因为Duplicate表只有排序功能)。
于是它对应的rollup功能,也只能是多一个排序功能:

我们知道,多一个排序的组合,其实就意味着新增加一个索引(对比传统数据库新增索引的操作)。
吐槽:
说到这个排序组合(特指rollup功能的),Doris在这里有一个限制,叫前缀索引,这个前缀索引限定了字段组合的长度必须要在36个字节以内,而且还有个我认为比较奇葩的规定,如果遇到了varchar类型字段,这个长度限制就一下子变成了20个字节。
比如,在我的这个案例里,我给一张Duplicate表增加一个rollup:

原本想着增加一个(domain,time,target_ip)这个字段组合的排序,但是实际上,因为这个varchar类型的20字节限制,导致它实际的排序字段只有(domain)一个,从对rollup的属性中是可以看到的:

老实说,我没有太明白为什么要做这样的限制,官网也没有就这一限制做出具体的解释(至少没有在显眼的地方说明),如果是考虑到排序效率,那是不是把这个限制作为一个可调整的参数,要更合理一些呢。
毕竟,对于有些场景来说,我是不在意数据写入慢一点的,只要这个慢能换回我多变,且高效的查询效率,我是愿意去接受的,不是吗?
2. 再看物化视图
从上面的案例可以看出来,rollup其实是对基础表在查询功能上,针对比较复杂的查询条件(聚合查询,非索引字段查询)起到一个加速的作用。
但是rollup的限制在于:
1,如果基础表为聚合模型表(Aggregate或者Unique),rollup提供的额外聚合功能,只能跟原本的value字段的聚合类型保持一致,局限性比较大;
2,如果基础表为Duplicate模型表,那么rollup就只能提供新的排序功能,无法提供聚合能力。
而物化视图,虽然其使用方式跟rollup类似,官方的描述是,但凡rollup支持的功能,物化视图都支持(0.13版本之后),而且它打破了rollup的一些限制。
比如,对于上面的第2点限制来说,有时就比较蛋疼,意味着如果是Duplicate模型表,我如果想对其中一些聚合操作,就没有办法利用到rollup的加速功能。
比如,我要在一张Duplicate模型表中执行如下聚合操作:


因为没有利用到任何加速特性,因此这个scanNode指向源表名本身,这个查询耗时为4.6秒。
那么我们针对这个查询(聚合)情况,对它创建个物化视图(rollup在当前场景下是做不到的)试试:

当确认后台创建成功之后:

再执行这个聚合查询:

发现,执行时间只需要不到0.1秒。
查看执行计划会发现,它利用了物化视图的加速:

可以看到,物化视图提供了某些场景下rollup做不到的加速功能。
但是呢,对于个别我认为非常简单常见的聚合功能,使用物化视图时却有坑。
3. 有个坑
来,我们再来看一个特别简单的聚合需求:统计每个上网ip的上网次数。
就是这么一个如此简单的需求,对于一个Duplicate表来说,其查询语句一般我们会这么来写:

在没有任何查询加速的加持下,这个查询一共用了4秒多的时间。
这个时候,我自然会想到用物化视图功能来对其做个加速(因为rollup实现不了),于是我打算根据上面那个物化视图创建成功的例子,在这里再创建一个:

但是结果却给我报了这个错,说出现了重复的列(client_ip),意思是在这个物化视图创建语句中,同一个字段,不能出现多次,这里在select之后,出现了一个client_ip,以及count(client_ip),被认为是非法的语法。
老实说,这个限制有点难以理解,同样的物化视图创建方式,CK肯定是可以的。
那要解决这个client_ip数量的统计,该怎么办呢?只能这样:

把第二个client_ip换成其他字段,就可以了,也能达到统计client_ip数量的要求。
当然,创建好之后,后续的查询,如果想要用到这个物化视图加速,查询语句也必须对应的改为这样:

看这个查询时间,就知道刚才创建的物化视图肯定是起作用了。

但是,查看这个物化视图的实现原理后,你会发现,它并不能100%精确统计client_ip的数量:

从图中框出的内容可以看出来,只有domain这个字段不为空时,这个间接对client_ip数量的统计才是100%准确的,否则就会有误差。
对于这一点,希望你在使用的时候一定要引起注意,不知道Doris社区在后续版本是否会改进这个问题,或者说有更好的解决方案。
4. 总结一下
今天这篇文章,咱们聊了下Doris的rollup功能和物化视图功能,这两个功能的共同目的,都是为了满足对基础表复杂的查询而加速用的。
rollup的创建更加简单,而物化视图的创建过程则要稍微复杂一点点,但这两者的使用对于使用而言都是无感知的,它们都必须依附于基础表而使用。
所以对于Doris的任何一张表来说,都有可能包含若干个rollup或者物化视图,想要清楚的知道一张表的使用情况,可以用这个命令来查看:

它可以看出当前表做了哪些加速手段,对应的加速字段是哪些,能更方便你去使用这张表。
最后想吐槽一下的就是:明明rollup跟物化视图都是为了加速基础表查询功能的,很多地方都有相似之处。但是官方的文档,却把这两者的描述,放在了完全不同的章节,难免会给人带来一定的阅读障碍。
Doris的物化视图功能跟CK相比呢,应该说都各有优劣,都能解决实际生产中不同复杂查询需求的查询效率问题,都是在利用硬件空间,换取查询时间的一种tradeoff。
