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

时间:2022-11-15 16:14:41

作者:StarRocks Committer 李雪岩,国双科技技术架构师、StarRocks Active Contributor 龚磊(本文为作者在 StarRocks Summit Asia 2022 上的分享)

本文先介绍物化视图的一些需求分析,看看现在的物化视图哪些地方做得好、哪些地方做得不好,然后再针对这些需求进行设计。然后再讲一下具体的实现原理,最后再讲一下 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、异步刷新的解决方案

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

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

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

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

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

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

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

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

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

 

#03

物化视图的实现原理

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

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

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

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

​再来看一下物化视图的整体框架。我们在新的版本中主要实现了多表异步物化视图,那么就需要一个异步物化视图的调度框架及相应的一些实现逻辑。以上图举例,比如在创建物化视图以及刷新物化视图的时候,我们都需要有对应的 Task,以及 TaskRun 执行单元去做相应的处理。

核心的实现内容实际上包含以下三方面的技术:Task 调度框架、分区刷新维护、Insert Overwrite。Task 调度框架解决的是物化视图异步刷新的问题,分区刷新维护解决的是分区同步增删以及刷新的问题,Insert Overwrite 是刷新中的核心技术。

 

1、Task 调度框架

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

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

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

 

2、分区刷新维护

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

在创建物化视图的时候,实际上我们指定了物化视图跟某一个 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

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

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

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

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

​当用户修改 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,优化刷新效率、增加部分刷新的语法和配置来应对复杂的刷新问题和各种复杂场景,大家可以尽请期待。

 

关于 StarRocks 

面世两年多来,StarRocks 一直专注打造世界*的新一代极速全场景 MPP 数据库,帮助企业建立“极速统一”的数据分析新范式,助力企业全面数字化经营。

当前已经帮助腾讯、携程、顺丰、Airbnb 、滴滴、京东、众安保险等超过 170 家大型用户构建了全新的数据分析能力,生产环境中稳定运行的 StarRocks 服务器数目达数千台。 

2021 年 9 月,StarRocks 源代码开放,在 GitHub 上的星数已超过 3500 个。StarRocks 的全球社区飞速成长,至今已有超百位贡献者,社群用户突破 7000 人,吸引几十家国内外行业头部企业参与共建。