Skip to content
chenhao edited this page Dec 25, 2018 · 1 revision

查询优化实践之Rollup

在Doris里Rollup作为一份聚合物化视图,其在查询中可以起到两个作用:

  • 索引
  • 聚合数据(仅用于聚合模型,即aggregate key)

但是为了命中Rollup需要满足一定的条件,并且可以通过执行计划中ScanNdoe节点的PreAggregation的值来判断是否可以命中Rollup,以及Rollup字段来判断命中的是哪一张Rollup表。

名词解释

Base表:基表。

Rollup:一般指基于Base表创建的Rollup表,但在一些场景包括Base以及Rollup表。

索引

前面的查询实践中已经介绍过Doris的前缀索引,即Doris会把Base/Rollup表中的前36个字节(有varchar类型则可能导致前缀索引不满36个字节,varchar会截断前缀索引,并且最多使用varchar的20个字节)在底层存储引擎单独生成一份排序的稀疏索引数据(数据也是排序的,用索引定位,然后在数据中做二分查找),然后在查询的时候会根据查询中的条件来匹配每个Base/Rollup的前缀索引,并且选择出匹配前缀索引最长的一个Base/Rollup。

       -----> 从左到右匹配
+----+----+----+----+----+----+
| c1 | c2 | c3 | c4 | c5 |... |

如上图,取查询中where以及on上下推到ScanNode的条件,从前缀索引的第一列开始匹配,检查条件中是否有这些列,有则累计匹配的长度,直到匹配不上或者36字节结束(varchar类型的列只能匹配20个字节,并且会匹配不足36个字节截断前缀索引),然后选择出匹配长度最长的一个Base/Rollup,下面举例说明,创建了一张Base表以及四张rollup:

+---------------+-------+--------------+------+-------+---------+-------+
| IndexName     | Field | Type         | Null | Key   | Default | Extra |
+---------------+-------+--------------+------+-------+---------+-------+
| test          | k1    | TINYINT      | Yes  | true  | N/A     |       |
|               | k2    | SMALLINT     | Yes  | true  | N/A     |       |
|               | k3    | INT          | Yes  | true  | N/A     |       |
|               | k4    | BIGINT       | Yes  | true  | N/A     |       |
|               | k5    | DECIMAL(9,3) | Yes  | true  | N/A     |       |
|               | k6    | CHAR(5)      | Yes  | true  | N/A     |       |
|               | k7    | DATE         | Yes  | true  | N/A     |       |
|               | k8    | DATETIME     | Yes  | true  | N/A     |       |
|               | k9    | VARCHAR(20)  | Yes  | true  | N/A     |       |
|               | k10   | DOUBLE       | Yes  | false | N/A     | MAX   |
|               | k11   | FLOAT        | Yes  | false | N/A     | SUM   |
|               |       |              |      |       |         |       |
| rollup_index1 | k9    | VARCHAR(20)  | Yes  | true  | N/A     |       |
|               | k1    | TINYINT      | Yes  | true  | N/A     |       |
|               | k2    | SMALLINT     | Yes  | true  | N/A     |       |
|               | k3    | INT          | Yes  | true  | N/A     |       |
|               | k4    | BIGINT       | Yes  | true  | N/A     |       |
|               | k5    | DECIMAL(9,3) | Yes  | true  | N/A     |       |
|               | k6    | CHAR(5)      | Yes  | true  | N/A     |       |
|               | k7    | DATE         | Yes  | true  | N/A     |       |
|               | k8    | DATETIME     | Yes  | true  | N/A     |       |
|               | k10   | DOUBLE       | Yes  | false | N/A     | MAX   |
|               | k11   | FLOAT        | Yes  | false | N/A     | SUM   |
|               |       |              |      |       |         |       |
| rollup_index2 | k9    | VARCHAR(20)  | Yes  | true  | N/A     |       |
|               | k2    | SMALLINT     | Yes  | true  | N/A     |       |
|               | k1    | TINYINT      | Yes  | true  | N/A     |       |
|               | k3    | INT          | Yes  | true  | N/A     |       |
|               | k4    | BIGINT       | Yes  | true  | N/A     |       |
|               | k5    | DECIMAL(9,3) | Yes  | true  | N/A     |       |
|               | k6    | CHAR(5)      | Yes  | true  | N/A     |       |
|               | k7    | DATE         | Yes  | true  | N/A     |       |
|               | k8    | DATETIME     | Yes  | true  | N/A     |       |
|               | k10   | DOUBLE       | Yes  | false | N/A     | MAX   |
|               | k11   | FLOAT        | Yes  | false | N/A     | SUM   |
|               |       |              |      |       |         |       |
| rollup_index3 | k4    | BIGINT       | Yes  | true  | N/A     |       |
|               | k5    | DECIMAL(9,3) | Yes  | true  | N/A     |       |
|               | k6    | CHAR(5)      | Yes  | true  | N/A     |       |
|               | k1    | TINYINT      | Yes  | true  | N/A     |       |
|               | k2    | SMALLINT     | Yes  | true  | N/A     |       |
|               | k3    | INT          | Yes  | true  | N/A     |       |
|               | k7    | DATE         | Yes  | true  | N/A     |       |
|               | k8    | DATETIME     | Yes  | true  | N/A     |       |
|               | k9    | VARCHAR(20)  | Yes  | true  | N/A     |       |
|               | k10   | DOUBLE       | Yes  | false | N/A     | MAX   |
|               | k11   | FLOAT        | Yes  | false | N/A     | SUM   |
|               |       |              |      |       |         |       |
| rollup_index4 | k4    | BIGINT       | Yes  | true  | N/A     |       |
|               | k6    | CHAR(5)      | Yes  | true  | N/A     |       |
|               | k5    | DECIMAL(9,3) | Yes  | true  | N/A     |       |
|               | k1    | TINYINT      | Yes  | true  | N/A     |       |
|               | k2    | SMALLINT     | Yes  | true  | N/A     |       |
|               | k3    | INT          | Yes  | true  | N/A     |       |
|               | k7    | DATE         | Yes  | true  | N/A     |       |
|               | k8    | DATETIME     | Yes  | true  | N/A     |       |
|               | k9    | VARCHAR(20)  | Yes  | true  | N/A     |       |
|               | k10   | DOUBLE       | Yes  | false | N/A     | MAX   |
|               | k11   | FLOAT        | Yes  | false | N/A     | SUM   |
+---------------+-------+--------------+------+-------+---------+-------+

三张表的前缀索引分别为Base(k1 ,k2, k3, k4, k5, k6, k7),rollup_index1(k9),rollup_index2(k9), rollup_index3(k4, k5, k6, k1, k2, k3, k7),rollup_index4(k4, k6, k5, k1, k2, k3, k7)能用的上前缀索引的列上的条件需要是= < > <= >= in between这些并且这些条件是并列的且关系即用and连接,对于or、!=等这些不能命中,然后看以下查询:

select * from test where k1 = 1 and k2 > 3;

有k1以及k2上的条件,检查只有Base的第一列含有条件里的k1,所以匹配最长的前缀索引即test,explain一下:

|   0:OlapScanNode                                                                                                                                                                                                                                                                                                                                                                                                 	|
|      TABLE: test                                                                                                                                                                                                                                                                                                                                                                                                  	|
|      PREAGGREGATION: OFF. Reason: No AggregateInfo                                                                                                                                                                                                                                                                                                                                                                	|
|      PREDICATES: `k1` = 1, `k2` > 3                                                                                                                                                                                                                                                                                                                                                                               	|
|      partitions=1/1                                                                                                                                                                                                                                                                                                                                                                                               	|
|      rollup: test                                                                                                                                                                                                                                                                                                                                                                                                 	|
|      buckets=1/10                                                                                                                                                                                                                                                                                                                                                                                                 	|
|      cardinality=-1                                                                                                                                                                                                                                                                                                                                                                                               	|
|      avgRowSize=0.0                                                                                                                                                                                                                                                                                                                                                                                               	|
|      numNodes=0                                                                                                                                                                                                                                                                                                                                                                                                   	|
|      tuple ids: 0    

再看以下查询:

select * from test where k9 in ("xxx", "yyyy") and k1 = 10;

有k4以及k5的条件,检查rollup_index3、rollup_index4的第一列含有k4,但是rollup_index3的第二列含有k5,所以匹配的前缀索引最长。

|   0:OlapScanNode                                                                                                                                                                                                                                                                                                                                                                                                	|
|      TABLE: test                                                                                                                                                                                                                                                                                                                                                                                                  	|
|      PREAGGREGATION: OFF. Reason: No AggregateInfo                                                                                                                                                                                                                                                                                                                                                                	|
|      PREDICATES: `k4` >= 1, `k5` > 3                                                                                                                                                                                                                                                                                                                                                                              	|
|      partitions=1/1                                                                                                                                                                                                                                                                                                                                                                                               	|
|      rollup: rollup_index3                                                                                                                                                                                                                                                                                                                                                                                        	|
|      buckets=10/10                                                                                                                                                                                                                                                                                                                                                                                                	|
|      cardinality=-1                                                                                                                                                                                                                                                                                                                                                                                               	|
|      avgRowSize=0.0                                                                                                                                                                                                                                                                                                                                                                                               	|
|      numNodes=0                                                                                                                                                                                                                                                                                                                                                                                                   	|
|      tuple ids: 0  

现在我们尝试匹配含有varchar列上的条件,如下:

select * from test where k9 in ("xxx", "yyyy") and k1 = 10;

有k9以及k1两个条件,rollup_index1以及rollup_index2的第一列都含有k9,按理说这里选择这两个rollup都可以命中前缀索引并且效果是一样的随机选择一个即可(因为这里varchar刚好20个字节,前缀索引不足36个字节被截断),但是当前策略这里还会继续匹配k1,因为rollup_index1的第二列为k1,所以选择了rollup_index1,其实后面的k1条件并不会起到加速的作用。(如果对于前缀索引外的条件需要其可以起到加速查询的目的,可以通过建立Bloom Filter过滤器加速,具体help alter table,一般对于字符串类型建立即可,因为Doris针对列存在Block级别对于整形、日期已经有Min/Max索引) 以下是explain的结果。

|   0:OlapScanNode                                                                                                                                                                                                                                                                                                                                                                                                  	|
|      TABLE: test                                                                                                                                                                                                                                                                                                                                                                                                  	|
|      PREAGGREGATION: OFF. Reason: No AggregateInfo                                                                                                                                                                                                                                                                                                                                                                	|
|      PREDICATES: `k9` IN ('xxx', 'yyyy'), `k1` = 10                                                                                                                                                                                                                                                                                                                                                               	|
|      partitions=1/1                                                                                                                                                                                                                                                                                                                                                                                               	|
|      rollup: rollup_index1                                                                                                                                                                                                                                                                                                                                                                                        	|
|      buckets=1/10                                                                                                                                                                                                                                                                                                                                                                                                 	|
|      cardinality=-1                                                                                                                                                                                                                                                                                                                                                                                               	|
|      avgRowSize=0.0                                                                                                                                                                                                                                                                                                                                                                                               	|
|      numNodes=0                                                                                                                                                                                                                                                                                                                                                                                                   	|
|      tuple ids: 0    

最后看一个多张Rollup都可以命中的查询:

select * from test where k4 < 1000 and k5 = 80 and k6 >= 10000;

有k4,k5,k6三个条件,rollup_index3以及rollup_index4的前3列分别含有这三列,所以两者匹配的前缀索引长度一致,选取两者都可以,当前默认的策略为选取了比较早创建的一张rollup,这里为rollup_index3。

|   0:OlapScanNode                                                                                                                                                                                                                                                                                                                                                                                                  	|
|      TABLE: test                                                                                                                                                                                                                                                                                                                                                                                                  	|
|      PREAGGREGATION: OFF. Reason: No AggregateInfo                                                                                                                                                                                                                                                                                                                                                                	|
|      PREDICATES: `k4` < 1000, `k5` = 80, `k6` >= 10000.0                                                                                                                                                                                                                                                                                                                                                          	|
|      partitions=1/1                                                                                                                                                                                                                                                                                                                                                                                               	|
|      rollup: rollup_index3                                                                                                                                                                                                                                                                                                                                                                                        	|
|      buckets=10/10                                                                                                                                                                                                                                                                                                                                                                                                	|
|      cardinality=-1                                                                                                                                                                                                                                                                                                                                                                                               	|
|      avgRowSize=0.0                                                                                                                                                                                                                                                                                                                                                                                               	|
|      numNodes=0                                                                                                                                                                                                                                                                                                                                                                                                   	|
|      tuple ids: 0    

如果稍微修改上面的查询为:

select * from test where k4 < 1000 and k5 = 80 or k6 >= 10000;

则这里的查询不能命中前缀索引。(甚至Doris存储引擎内的任何Min/Max,BloomFilter索引都不能起作用)

聚合数据(仅用于聚合模型,即aggregate key)

当然一般的聚合物化视图其聚合数据的功能是必不可少的,这类物化视图对于聚合类查询或报表类查询都有非常大的帮助,要命中聚合物化视图需要下面一些前提:

  1. 查询或者子查询中涉及的所有列都存在一张独立的Rollup中。
  2. 如果查询或者子查询中有Join,则Join的类型需要是Inner join。

以下是可以命中Rollup的一些聚合查询的种类,

+--------------+-------+-------------------------+-------+-------+-------+
|列类型\查询类型|  Sum  | Distinct/Count Distinct |   Min |  Max  |  Ndv  |
+--------------+-------+-------------------------+-------+-------+-------+
|     Key      | false |           true          |  true |  true | true  |
+--------------+-------+-------------------------+-------+-------+-------+
|   Value(Sum) |  true |          false          | false | false | false |
+--------------+-------+-------------------------+-------+-------+-------+
|Value(Replace)| false |          false          | false | false | false |
+--------------+-------+-------------------------+-------+-------+-------+
|   Value(Min) | false |          false          |  true | false | false |
+--------------+-------+-------------------------+-------+-------+-------+
|   Value(Max) | false |          false          | false | true  | false |
+--------------+-------+-------------------------+-------+-------+-------+

如果符合上述条件,则针对聚合模型在判断命中rollup的时候会有两个阶段:

  1. 首先通过条件匹配出命中前缀索引索引最长的Rollup表,见上述索引策略。
  2. 然后比较Rollup的行数,选择最小的一张Rollup。

如下Base表以及Rollup:

+-------------+-------+--------------+------+-------+---------+-------+
| IndexName   | Field | Type         | Null | Key   | Default | Extra |
+-------------+-------+--------------+------+-------+---------+-------+
| test_rollup | k1    | TINYINT      | Yes  | true  | N/A     |       |
|             | k2    | SMALLINT     | Yes  | true  | N/A     |       |
|             | k3    | INT          | Yes  | true  | N/A     |       |
|             | k4    | BIGINT       | Yes  | true  | N/A     |       |
|             | k5    | DECIMAL(9,3) | Yes  | true  | N/A     |       |
|             | k6    | CHAR(5)      | Yes  | true  | N/A     |       |
|             | k7    | DATE         | Yes  | true  | N/A     |       |
|             | k8    | DATETIME     | Yes  | true  | N/A     |       |
|             | k9    | VARCHAR(20)  | Yes  | true  | N/A     |       |
|             | k10   | DOUBLE       | Yes  | false | N/A     | MAX   |
|             | k11   | FLOAT        | Yes  | false | N/A     | SUM   |
|             |       |              |      |       |         |       |
| rollup2     | k1    | TINYINT      | Yes  | true  | N/A     |       |
|             | k2    | SMALLINT     | Yes  | true  | N/A     |       |
|             | k3    | INT          | Yes  | true  | N/A     |       |
|             | k10   | DOUBLE       | Yes  | false | N/A     | MAX   |
|             | k11   | FLOAT        | Yes  | false | N/A     | SUM   |
|             |       |              |      |       |         |       |
| rollup1     | k1    | TINYINT      | Yes  | true  | N/A     |       |
|             | k2    | SMALLINT     | Yes  | true  | N/A     |       |
|             | k3    | INT          | Yes  | true  | N/A     |       |
|             | k4    | BIGINT       | Yes  | true  | N/A     |       |
|             | k5    | DECIMAL(9,3) | Yes  | true  | N/A     |       |
|             | k10   | DOUBLE       | Yes  | false | N/A     | MAX   |
|             | k11   | FLOAT        | Yes  | false | N/A     | SUM   |
+-------------+-------+--------------+------+-------+---------+-------+

看以下查询:

select sum(k11) from test_rollup where k1 = 10 and k2 > 200 and k3 in (1,2,3);

首先判断查询是否可以命中聚合的Rollup表,经过查上面的图是可以的,然后条件中含有k1,k2,k3三个条件,这三个条件test_rollup、rollup1、rollup2的前三列都含有,所以前缀索引长度一致,然后比较行数显然rollup2的聚合程度最高行数最少所以选取Rollup2。

|   0:OlapScanNode                                          |
|      TABLE: test_rollup                                   |
|      PREAGGREGATION: ON                                   |
|      PREDICATES: `k1` = 10, `k2` > 200, `k3` IN (1, 2, 3) |
|      partitions=1/1                                       |
|      rollup: rollup2                                      |
|      buckets=1/10                                         |
|      cardinality=-1                                       |
|      avgRowSize=0.0                                       |
|      numNodes=0                                           |
|      tuple ids: 0                                         |
+-----------------------------------------------------------+