查看原文
其他

Hive优化器原理与源码解析系列--优化规则AggregateProjectPullUpConstantsRule(十七)

后羿BigDataplus BigDataplus 2021-10-15


目录

背景

优化规则AggregateProjectPullUpConstantsRule

  • matches方法逻辑详解

  • onMatch方法逻辑详解

总结


背景

        这篇文章来讲优化规则AggregateProjectPullUpConstantsRule,顾名思义是将Aggregate汇总操作中常量字段上拉到Project投影操作中的优化规则,主要功能从Aggregate聚合中删除常量键。常量字段是使用RelMetadataQuery.getpulldupredicates(RelNode)推导的,其输入不一定必须是Project投影操作。但此Rule规则从不删除最后一列,简单来讲,如果groupBy字段只有一列,而且为常量,也不会执行此优化,因为聚合Aggregate([])返回1行,即使其输入为空。由于转换后的关系表达式必须与原始关系表达式匹配,为等价变换,因此常量被放置在简化聚合Aggregate上方的Project投影中。

举例说明:

如员工信息表:EMPLOYEE

id ID标识

name  姓名

sex 性别(f:女性 m:男性)

city 城市


待优化前SQL语句:



SELECT

    city,

    sex,

    COUNT(id)

FROM EMPLOYEE

WHERE sex = 'f'

GROUP BY city,sex


通过AggregateProjectPullUpConstantsRule优化规则等价变换后,优化后SQL语句:

SELECT

    city,

    'f' as sex,

    emp_cnt

(

    SELECT

        city,

        COUNT(id) as emp_cnt

    FROM EMPLOYEE

    WHERE sex = 'f'

    GROUP BY city

)

        通过从等值谓词中识别GroupBy所引用sex字段值一直为常量'f',于是把Aggregate聚合中GroupBy中sex分组字段移除,在Aggregate操作之上创建一个Project投影,并把GroupBy删除sex常量'f',放置其中,这样就完成了Aggregate操作中常量上拉。

        上述这些操作AggregateProjectPullUpConstantsRule优化规则是如何做到的,应用此条规则需要满足哪些条件,接下来详细讲解。


优化规则AggregateProjectPullUpConstantsRule

1)matches方法逻辑详解

        matches方法返回此规则Rule是否可能与给定的操作数operands匹配,但是此方法的任何实现都可以给出误报,也就是说虽然规则与操作数匹配,但随后OnMatch(ReloptRuleCall)而不生成任何后续任务。

        判断由RelOptCall调用的优化规则Rule是否与输入参数RelNode关系表达式匹配,即此优化规则Rule能否应用到一个RelNode关系表达式树上。但此matches方法是继承自父类方法,默认返回true。

public boolean matches(RelOptRuleCall call) { return true;}


2)onMatch方法逻辑详解

        接收有关一条规则匹配的通知。同时此方法被调用,call.rels保存了与规则Rule的操作数Operands匹配上的关系表达式RelNode集合;call.rels[0]是根表达式。通常一条规则Rule会检查这些节点是否有效匹配,创建一个新表达式RelNode(等价的)然后调用RelOptRuleCall.transformTo(org.apache.calcite.rel.RelNode, java.util.Map<org.apache.calcite.rel.RelNode, org.apache.calcite.rel.RelNode>)注册表达式。而RelOptRuleCall用一系列RelNode关系表达式集合作为参数,对RelOptRule优化规则的调用。

        首先call.rel(0)获取Aggregate操作对象,并取得groupBy引用字段的个数,如果只有GroupBy只有一个字段,已经没有优化的空间,不可能把一个非空groupby转换为空groupBy,即不可能移除仅有一个常量字段。

final Aggregate aggregate = call.rel(0);final RelNode input = call.rel(1);final int groupCount = aggregate.getGroupCount();//返回groupBy 字段的个数 if (groupCount == 1) {//如果groupBy仅引用一个字段,则退出优化 return;}

        哪些是常量字段是RelMetadataQuery.getpulldupredicates(RelNode)提取出关于此输入RelNode的谓词,返回RelOptPredicateList对象推导的。

RelOptPredicateList:

已知保存在特定关系表达式输出中的谓词。

  • 上拉谓词:(字段pulldupredicates是应用于关系表达式输出的每一行的谓词。它们是从输入关系表达式和关系运算符推断出来的。

            例如,如果将Filter(x>1)应用于谓词y<10的关系表达式,则过滤器的上拉谓词为[y<10,x>1]。

  • 推断谓词:仅适用于联接。如果联接的左输入上有谓词,并且该谓词位于联接条件中使用的列上,则可以在联接的右输入上推断谓词。(反之亦然。)

如:

SELECT *FROM empJOIN dept ON emp.deptno = dept.deptno

WHERE emp.gender = 'F' AND emp.deptno < 10

说明:

  • 左侧: Filter(Scan(EMP), deptno < 10, predicates: [deptno < 10]

  • 右侧: Scan(DEPT), predicates: []关联Join: Join(left, right, emp.deptno = dept.deptno, leftInferredPredicates: [],

  • 右侧推断谓词rightInferredPredicates: [deptno < 10],

  • 上拉谓词pulledUpPredicates: [emp.gender = 'F', emp.deptno < 10, emp.deptno = dept.deptno, dept.deptno < 10]

注意:来自左输入的谓词出现在rightInferredPredicates中。来自多个源的谓词出现在pulledUpPredicates中。

        那么RelOptPredicateList对象的表现形式:[emp.gender = 'F', emp.deptno < 10, emp.deptno = dept.deptno, dept.deptno < 10],如果没有从此RelNode提取的谓词为null,则优化无法继续。

        ReduceExpressionsRule.predicateConstants方法把RelOptPredicateList对象提取出等值谓词表达式,如上述的emp.gender = 'F'。以<emp.gender,'F'>形式映射存放在变量constants。遍历GroupBy引用字段的索引,并包装成RexInputRef(序号,字段数据类型)代表一个字段。如果在常量等值谓词映射关系中存在的。则以<字段索引,常量值>映射关系存在,如上述的<字段序号,'F'>。

        同样,如果GroupBy后没引用常量字段或引用常量字段没有在等值常量谓词中出现,则推出优化。

final RexBuilder rexBuilder = aggregate.getCluster().getRexBuilder();final RelMetadataQuery mq = RelMetadataQuery.instance();final RelOptPredicateList predicates = mq.getPulledUpPredicates(aggregate.getInput());//参数为聚合的子RelNodeif (predicates == null) {//如果没有提取出谓词predicate,则推出优化 return;}final ImmutableMap<RexNode, RexNode> constants = ReduceExpressionsRule.predicateConstants(RexNode.class, rexBuilder, predicates);final NavigableMap<Integer, RexNode> map = new TreeMap<>();for (int key : aggregate.getGroupSet()) {//遍历GroupBy后的字段的序号(index) final RexInputRef ref = rexBuilder.makeInputRef(aggregate.getInput(), key);//包装成RexInputRef(序号,字段数据类型)代表 一个字段 if (constants.containsKey(ref)) {//判断是否存在 map.put(key, constants.get(ref)); }}if (map.isEmpty()) { //如果groupBy引用的字段,都不是常量,则退出优化 return;}if (groupCount == map.size()) { //如果groupBy个数全是常量项的话,则删除。不能全部上拉 map.remove(map.navigableKeySet().first());}

        最后, 如果groupBy个数全是常量项的话,则删除。但“分组依据”中至少需要一个项目。否则,“GROUP BY 1,2”可能会更改为“GROUP BY()”。移除第一个元素在这里不是最优的,不过,它将允许我们使用下面的快速路径(只需修剪groupCount)。

        创建上拉的Aggregate聚合操作,移除聚合中使用的常量。

        遍历aggregate.getGroupSet()返回对象GroupBy字段的位图索引,判断如果在常量map中存在,则删除。这也是删除GroupBy常量的关键部分(哪些常量是可以删除,仔细看前面讲过的,生成删除后的新newGroupSet。创建删除常量后的新Aggregate对象。

ImmutableBitSet newGroupSet = aggregate.getGroupSet();//Returns a bit set of the grouping fields.for (int key : map.keySet()) { newGroupSet = newGroupSet.clear(key); //清除GroupBy中引用的常量字段,生成新的newGroupSet对象}final int newGroupCount = newGroupSet.cardinality();//如果常量在组列表的后端,我们只需减少组计数。后面默认舍掉final RelBuilder relBuilder = call.builder();relBuilder.push(input);// Clone aggregate calls.final List<AggregateCall> newAggCalls = new ArrayList<>();for (AggregateCall aggCall : aggregate.getAggCallList()) { newAggCalls.add( aggCall.adaptTo(input, aggCall.getArgList(), aggCall.filterArg, groupCount, newGroupCount));}relBuilder.aggregate( relBuilder.groupKey(newGroupSet, false, null), newAggCalls);//创建删除了GroupBy常量的汇总aggregate

AggregateCall:在Aggregate聚合操作中聚合方法的调用

adaptTo()方法:创建一个等效的AggregateCall,它适用于新的输入类型和/或GROUP BY中的列数。


        将上面GroupBy中移除后的常量,放置在新创建的Project投影。

遍历aggregate引用的所有字段列表(包括聚合方法内的字段),如果是聚合方法表达式,名称和位置不变,如果是常量则直接提取出常量值,如'F' 作为字段值放置到Project中。其他依次递增放置到以添加到Pair<字段表达式,字段名称>列表中。

// Create a projection back again.List<Pair<RexNode, String>> projects = new ArrayList<>();int source = 0;for (RelDataTypeField field : aggregate.getRowType().getFieldList()) {//遍历聚合的字段列表 RexNode expr; final int i = field.getIndex(); if (i >= groupCount) { //聚合中的使用字段,不是GroupBy中的字段,则名称和位置不变 // Aggregate expressions' names and positions are unchanged. expr = relBuilder.field(i - map.size()); } else if (map.containsKey(i)) {//如果此字段 是常量字段,则把此字段放置到Project中。 expr = map.get(i);//从常量映射中,取出常量值,放置到Project中 } else { expr = relBuilder.field(source);//否则,依次创建聚合表达式 ++source; } projects.add(Pair.of(expr, field.getName()));//添加到Pair<字段表达式,字段名称>}relBuilder.project(Pair.left(projects), Pair.right(projects)); // 以字段名称,字段表达式创建Project投影操作call.transformTo(relBuilder.build());

        代码最后部分,以<字段名称,字段表达式>创建Project投影操作,做等价变换注册到RelSet等价的关系表达式集合,已备优化器选择。


总结

        优化规则AggregateProjectPullUpConstantsRule将等值谓词常量中出现的,并在GroupBy中引用的字段进行删除,为了保证其等价变换再上拉到Project投影中,减少中间分组计算的过程。

        由于笔者知识及水平有限,因此文中错漏之处在所难免,恳请各位老师、专家不吝赐教。



往期文章分享


优化规则系列

Hive优化器原理与源码解析系列--优化规则SortRemoveRule(一)

Hive优化器原理与源码解析系列--优化规则SortJoinReduceRule(二)

Hive优化器原理与源码解析系列--优化规则SortProjectTransposeRule(三)

Hive优化器原理与源码解析系列--优化规则SortUnionReduceRule(四)

Hive优化器原理与源码解析系列--优化规则SortMergeRule(五)

Hive优化器原理与源码解析系列--优化规则ProjectFilterPullUpConstantsRule(六)

Hive优化器原理与源码解析系列--优化规则SortLimitPullUpConstantsRule(七)

Hive优化器原理与源码解析系列--优化规则UnionPullUpConstantsRule(八)

Hive优化器原理与源码解析系列--优化规则ProjectOverIntersectRemoveRule(九)

Hive优化器原理与源码解析系列--优化规则ProjectSortTransposeRule(十)

Hive优化器原理与源码解析系列--优化规则HiveProjectMergeRule(十一)

Hive优化器原理与源码解析系列--优化规则HiveJoinAddNotNullRule(十二)

Hive优化器原理与源码解析系列--优化规则HiveJoinCommuteRule(十三)

Hive优化器原理与源码解析系列--优化规则PartitionPruneRule(十四)

Hive优化器原理与源码解析系列--优化规则HivePreFilteringRule(十五)

Hive优化器原理与源码解析系列--优化规则HiveAggregateProjectMergeRule(十六)

成本模型系列

Hive优化器原理与源码解析系列—统计信息带谓词选择率Selectivity

Hive优化器原理与源码解析系列—统计信息之选择性

Hive优化器原理与源码解析系列—统计模块内存成本估算

Hive优化器原理与源码解析系列--统计信息中间结果大小计算

Hive优化器原理与源码解析系列—CBO成本模型CostModel(一)

Hive优化器原理与源码解析系列—CBO成本模型CostModel(二)

Hive优化器原理与源码解析系列—统计信息UniqueKeys列集合

Hive优化器原理与源码解析—统计信息Parallelism并行度计算

Hive优化器原理与源码解析—统计信息NDV唯一值数估算




: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存