StarRocks 技术内幕 | 多表物化视图的设计与实现

本文发表于: &{ new Date(1668441600000).toLocaleDateString() }

本文先介绍物化视图的一些需求分析,看看现在的物化视图哪些地方做得好、哪些地方做得不好,然后再针对这些需求进行设计。然后再讲一下具体的实现原理,最后再讲一下 StarRocks 2.5 版本的物化视图还会开发哪些功能。

 

#01 物化视图的需求分析

1.什么是物化视图

要了解物化视图可以先了解视图的概念。视图是一个虚拟表(也可以认为是一条语句),基于它创建时指定的查询语句返回的结果集。而物化视图则是将这个虚拟表进行实体化,其本身可以理解为是一个特殊的表。

2.物化视图的应用场景

物化视图最常见的场景是,由基础的 Base 表通过创建物化视图的 SQL 生成物化视图,当用户查询相似的 SQL 时,查询优化器可以自动 QueryRewrite 复用物化视图,从而达到查询加速的效果。

在 2.4 之前,我们仅支持的是单表同步的物化视图,但它缺乏一些复杂场景的支持,例如只能支持一些简单的 SQL。

对于一个实时的场景,比如用户有两张实时表进行 Join 操作,由于单表同步物化视图不支持多表 Join 操作,这种场景就无法支持了。

对于离线多表加速建模的场景,通常需要事实表和维度表的 Join 的操作。这里面有两方面的需求,一方面是加速的需求,希望我们在查这些 Base 表时通过 QueryRewrite 加速查询;另一方面是建模的需求,希望物化视图能够屏蔽后面的事实表和维度表,也就是说希望物化视图可以直接进行查询。

还有一类场景,这类场景虽然也可以支持,但是支持得不是很好,就是当物化视图的计算结果比较少的时候,希望分区分桶比较少,这样查询性能才会比较好。之前同步的模型,物化视图与 Base 表是一对一的关系,可能就会出现创建物化视图虽然结果很少,但是分区分桶很多,反而出现查询性能下降的现象。

根据这些场景和问题,接下来我们看看可以怎么去解决这些问题。

 

#02 物化视图的设计

1.同步解决方案

我们观察到 SQL 复杂度越低,数据的同步性越好做,当 SQL 复杂度越高,数据的同步性越来越难做。之前的同步物化视图,其实是选择了同步性最佳的点,但它的弊端就是 SQL 很复杂的时候很难做。但是用户的大部分场景 SQL 可能是很复杂的,并且可以接受一定的异步延迟,所以可以牺牲一定的同步性,满足复杂 SQL 加速的场景。于是就有了异步刷新的解决方案。

2.异步刷新的解决方案


 

 

首先我们看看怎么解决之前的实时场景的问题。在我们使用新版创建物化视图时,可以通过 PARTITION BY 关键字来指定物化视图跟某一个 Base 表的一个分区进行绑定,当前的版本分桶是必填的,但是分桶是可以灵活变化的,然后还可以指定刷新的起始时间点和间隔。

我们创建物化视图语句里面的 Query 语句本身是基本没有任何限制的,可以写得很复杂,只要是可以查询的基本都可行。对于实时的场景,它其实是只刷新某一个分区的小部分数据量。对于实时表,导入非常频繁的,用户可以接受分钟级刷新的场景下,用户可以使用周期性刷新,例如每 1 分钟刷新一次,这样可以避免刷新频率过高导致物化视图刷新触发过于频繁。


 

 

然后我们再看看离线的场景,离线场景 Base 表,通常事时表基本上只有每天才会去刷新某一个分区,维表会全量刷新一个分区。这里我们在创建物化视图时,可以指定 REFRESH ASYNC,当每个 Base 表有数据变化的时候,它会自动去判定哪些分区需要刷新并进行智能刷新,对于不需要刷新的分区就不刷新。离线场景也可以支持以天为周期进行调度。离线的场景下由于数据量比较大,有可能查询需要调整一些特殊的 Session Variable 参数才能够刷新成功,这些特殊的参数可以通过 Query 里面的 SELECT hint 来传入。

但是物化视图其是一个需要长期打磨的功能。周期性刷新和触发式刷新覆盖不了所有的场景,有可能用户在不需要刷新的时候,还花费了很多刷新的成本。所以我们提供了一个手动刷新的功能,让用户能灵活地控制刷新的时机,也就是通过指定 REFRESH MANUAL。等到物化视图在后面的版本越来越完善的时候,使用手动刷新的情况会渐渐较少。


 

 

另外之前还提到了分区分桶浪费的问题,关于解决这个问题的方法,可以通过指定PARTITION BY DATE_TRUNC("month", dt),这样我们就可以把 dt 这一列本来按天的 Base 表上卷到按月分区的表,从而来减少分区浪费的问题。同时我们也可以指定 Bucket 的数量,而不是跟 Base 表保持一致。这样在物化视图的结果很少的情况下,我们可以灵活减少分区和分桶,从而提高查询性能,避免分区分桶的浪费。

以上讲的这些都是 StarRocks 2.4 已经实现的功能,这些功能是 StarRocks 和社区共同讨论并实现的。在这里要感谢社区的小伙伴们。

 

#03 物化视图的实现原理

先来了解一下多表物化视图的框架,即数据模型。在早期的版本中,我们实际上支持的是单表同步物化视图,也就是我们基于一个原始表去创建物化视图,实际上物化视图是以索引的形式去存在的。

在左图中可以看到表的基础索引中的 Tablet 和物化视图索引的 Tablet 是一一对应,但是在多表异步的物化视图的框架中,Tablet 不是一一的对应关系,物化视图实际上是以表的模型去做实现的。以右图为例,假设 Base 表有两个 Partition A 和 B,假设物化视图有 Partition C,那么 Partition A、Partition B 的 Tablet 和物化视图的 Partition C 中的 Tablet 是映射关系,这种关系不是一一对应的关系。

再来看一下物化视图的整体框架。我们在新的版本中主要实现了多表异步物化视图,那么就需要一个异步物化视图的调度框架及相应的一些实现逻辑。以上图举例,比如在创建物化视图以及刷新物化视图的时候,我们都需要有对应的 Task,以及 TaskRun 执行单元去做相应的处理。核心的实现内容实际上包含以下三方面的技术:Task 调度框架、分区刷新维护、Insert Overwrite。Task 调度框架解决的是物化视图异步刷新的问题,分区刷新维护解决的是分区同步增删以及刷新的问题,Insert Overwrite 是刷新中的核心技术。

1.Task 调度框架

首先给大家介绍一下 Task 调度框架。Task 实际上是一个可重用的对象,是任务的存储模板。TaskRun 是其中真正的计算对象,每一个 TaskRun 都是基于 Task 这个模板去做实现的,是计算的最小单元。可以类比成 Java 的 Class,以及 Class 相应的一些实现 Object。在 Task 调度框架中,支持手动刷新、触发式刷新以及周期性刷新等三种刷新方式。

再来看调度框架的核心架构。调度框架包含这几方面的内容,一个是调度器,然后是 Task 和 TaskRun,Pending 队列、Running 队列以及 TaskRun history 集合。以手动刷新任务举例,首先基于 Task 去创建 TaskRun 对象,存放在 Pending 队列中。调度器会取出 Pending 队列中的 TaskRun 做相应的执行,并存放到 Running 队列中,同时会基于 TaskRun 运行的状态,进入到 TaskRun history 集合中。在调度框架中也有一些参数可以配置,比如现在我们队列长度默认是 500,并发数默认是 20,TaskRun 默认是清除是三天以上的历史。有了 Task 框架,我们还需要进行分区的刷新维护。

2.分区刷新维护

在创建物化视图的时候,实际上我们指定了物化视图跟某一个 Base 表分区的绑定关系,刷新框架会在刷新前增删分区,以保证物化视图的分区大于 Base 表绑定的分区。以图中举例,假设我们有这样一个表,它有三个分区 A、B、C,物化视图也有三个分对应的分区 A、B、C,它们是一一对应关系。除了这么种 1:1 的映射关系,实际上其中还有 1:n、n:1 以及 n:n 的映射关系。

有了分区映射关系,就可以基于分区的映射关系去做对应的一些刷新。我们是基于分区的版本去判断哪些分区需要做刷新。以图上举例,假设 Base 表有 1、2、3 三个分区,物化视图也有 1、2、3 三个分区,Base 表分区 1 的版本是 2,物化视图分区 1 的版本也是 2,这个时候我们是不需要去做刷新的。假设 Base 表的分区 2  版本是 4,而物化视图的分区 2 版本是 3,这个时候我们判断需要去做进一步刷新。那么怎么样去做刷新,实际上是依靠于我们底层的 Insert Overwrite 技术。

3.Insert Overwrite

相信有很多同学用过临时分区,实际上 Insert Overwrite 的原理就是内置的这一过程。通常有三个步骤,首先创建一个临时分区,然后把数据写到临时分区,最终将临时分区和目标分区做原子级别的替换。

那么以上是多表异步物化设图的三种核心技术的实现原理。除此我们还需要考虑物化视图失效的问题。

当用户修改 Base 表的结构时,比如删除了 Base 表的一个列时,这个时候物化视图可能会失效。在当前的版本中,物化视图仍然可以查询,但是它不能够被刷新。以上就是物化视图核心技术实现原理。

上述这些都是 2.4 版本已经实现的功能。StarRocks 2.4 版本是一个预览版本,需要通过设置 FE 参数 enable_experimental_mv 开启使用。

#04 StarRocks 2.5 版本展望

2.4 版本还有三个比较重要的功能没有实现。一是不支持从外表去创建物化视图,2.4 的版本只支持在一个数据 DB 上去创建物化视图,并不支持跨 DB 创建物化视图;二是不支持创建基于物化视图的物化视图;三是还不支持最重要的 QueryRewrite 功能。

2.5 版本会支持物化视图的查询改写,支持从外表创建物化视图,支持从物化视图创建物化视图,支持物化视图 TTL,优化刷新效率、增加部分刷新的语法和配置来应对复杂的刷新问题和各种复杂场景,大家可以尽请期待。