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

2022-04-25 15:34:54 浏览数 (1)

目录

背景

优化规则HiveProjectMergeRule

  • matches方法逻辑详解
  • onMatch方法逻辑详解

总结

背景

此篇文章讲解HiveProjectMergeRule优化规则,顶层Project投影操作(相当于HSQL中的Select操作)和底部Project投影操作进行合并的优化规则,但前提是这些Project不投影相同的输入引用集。此优化规则中,Hive只实现了matches匹配方法的判断逻辑部分,不支持在RelNode关系表达式树中含有Window窗口函数或Hive各种分析函数的的Project投影操作,而相关逻辑判断和优化的等价变换的RelNode关系表达式树的OnMatch函数是直接从Calcite 优化器优化规则父类ProjectMergeRule继承而来的。

学习和研究CBO优化器相关知识,对于初学者来说,稍微有一点点门槛。没入门之前总觉得优化器做的都是高大上的优化操作。其实优化器也做稍微熟练SQL开发者都能优化的事情,毕竟一款支持SQL数据库面对象的是各个层次开发者,所以优化器无论是简单和复杂的优化操作都得具备。就像此条优化规则HiveProjectMergeRule就像类似于一段SQL的内外层两个Select 操作进行合并减少重复操作,从而达到优化的目的。

同样,此条优化规则也不例外,也继承自父类RelOptRule来实现的。这里先讲述一下RelOptRule相关概念。

RelOptRule Calcite框架中的优化规则Rule的抽象类,功能就是把一个关系表达式RelNode1转换为另一个关系表达式RelNode2,它有一系列RelOptRuleOperands,其决定了此Rule是否能被应用到一棵RelNodes操作符数的指定部分section,由optimizer优化器指出哪些Rule是可应用的,然后在这些Rules规则上调用onMatch(RelOptRuleCall)方法。而RelOptRuleCall是优化规则调用,其使用一系列RelNode关系表达式集合作为参数,对RelOptRule优化规则的调用。匹配上优化规则内一系列RelOptRuleOperands操作数,也代表了优化规则Rule配上了输入参数RelNode树的某些子RelNode,可进行优化。

优化规则HiveProjectMergeRule

优化器的优化规则Rule实现,都需实现两个方法matches和OnMatch两个方法。

1)matches方法逻辑详解

matches方法返回此规则Rule是否可能与给定的操作数operands匹配。优化器在匹配上规则Rule的所有操作数Operands之后和调用OnMatch(ReloptRuleCall)之前调用此方法。在优化器的实现中,它可能会在调用OnMatch(ReloptRuleCall)之前将匹配的ReloptRuleCall排队很长时间,matches方法提前判断这种方法是有好处的,因为优化器可以在处理的早期,把不满足匹配条件的规则放弃掉。

判断由RelOptCall调用的优化规则Rule是否与输入参数RelNode关系表达式匹配,即此优化规则Rule能否应用到一个RelNode关系表达式树上。

当前Project投影操作中不支持一个窗口函数与另一个窗口函数的合并。即顶部Project投影操作中RexNode行表达式的序号位,对应与底部Project的相应的序号RexNode行表达式都是窗口函数,则matches返回false。RexOver继承RexCall表达式用来调用窗口Window函数的实现类,如果一个RexNode表达式是RexOver实例,则说明RexNode行表达式为窗口函数。

RexOver继承关系如下:

代码语言:javascript复制
public class RexOver
extends RexCall
Call to an aggregate function over a window.

其他默认返回true

代码语言:javascript复制
public boolean matches(RelOptRuleCall call) {
  //当前投影合并,不支持窗口函数
  final Project topProject = call.rel(0);//顶部Project操作
  final Project bottomProject = call.rel(1);//底部Project操作
  for (RexNode expr : topProject.getChildExps()) {//遍历顶部Project的输入行表达式
    if (expr instanceof RexOver) {
      Set<Integer> positions = HiveCalciteUtil.getInputRefs(expr);//返回当前字段或行表达式中索引的位置
      for (int pos : positions) {//顶Project中相应字段对应的位置来查找在底部Project投影中到行表达式,判读是否为窗口函数
        if (bottomProject.getChildExps().get(pos) instanceof RexOver) {
          return false;
        }
      }
    }
  }
  return super.matches(call);
}

总之,顶层Project中一个窗口函数中的字段来自底部Project中窗口函数中的字段,是不支持的。

2)onMatch方法逻辑详解

同样,首先使用RelOptRuleCall对象rel(0)方法获取根RelNode关系表达式Project topProject,其次获取Project的子RelNode关系表达式bottomProject。

代码语言:javascript复制
   final Project topProject = call.rel(0);
        final Project bottomProject = call.rel(1);
        final RelBuilder relBuilder = call.builder();

虽然在matches方法内优化规则Rule能否应用做了判断,但不会完全覆盖到,所以onMatch方法内也可做相应的判断,如果不满足条件也不会做任何优化变换而结束。

然后分别获取的顶部和底部的Project投影操作的Permutation对象。如果对象非空并是isIdentity为true,不再做任何优化return结束。

如果上述条件都不满足的话,则通过topPermutation.product(bottomPermutation)方法,通过分析序列化输出创建新的Permutation对象的字段Mapping(可参考前面Rule(十)何为Mapping),取出Permutation对象的字段和相应的数据类型,再使用上面push的底部Project操作子输入RelNode,然后构建器出新合并生成的Project投影操作等价变换后,注册等价关系表达式集合RelSet。

代码语言:javascript复制
final Permutation topPermutation = topProject.getPermutation();
        if (topPermutation != null) {
            if (topPermutation.isIdentity()) {
                // Let ProjectRemoveRule handle this.
                return;
            }
            final Permutation bottomPermutation = bottomProject.getPermutation();
            if (bottomPermutation != null) {
                if (bottomPermutation.isIdentity()) {
                    // Let ProjectRemoveRule handle this.
                    return;
                }
                //通过分析序列化输出创建Project
                final Permutation product = topPermutation.product(bottomPermutation);
                relBuilder.push(bottomProject.getInput());
                relBuilder.project(relBuilder.fields(product),
                        topProject.getRowType().getFieldNames());
                call.transformTo(relBuilder.build());
                return;
            }
        }

以下是继续对一棵关系表达式树是否满足匹配优化的条件判断。

在此优化规则内,force是判断Project合并是否为强制模式,如果force=true即强制模式,即使顶层Project和底部Project完全相同,也会执行合并动作。如果force=false即非强制模式,顶部和底部Project相同,则不会再做任何优化操作。RexUtil.isIdentity方法是判断两个表达式集合的个数和数据类型是否完全一致。

代码语言:javascript复制
if (!force) {
    if (RexUtil.isIdentity(topProject.getProjects(),
            topProject.getInput().getRowType())) {
        return;
    }
}

RelOptUtil.pushPastProject方法把顶部Project投影内RexNode行表达式和底部Project投影内RexNode行表达式进行合并成新的Project对象。

代码语言:javascript复制
//将基于Project投影输出字段的表达式列表转换为Project投影输入字段上的等效表达式。
static java.util.List<RexNode>    pushPastProject(java.util.List<? extends RexNode> nodes, Project project)

即使顶部和底部Project操作合并后生成新的Project投影操作newProjects,使用RexUtil.isIdentity判断newProjects与底部Project的输入子RelNode做一次是否相同,如果是强制模式即force=true,直接调用call.transformTo把底部Project投影的子输入RelNode注册RelSet集合内,做一次等价变换然后返回return结束。

代码语言:javascript复制
final List<RexNode> newProjects =
        RelOptUtil.pushPastProject(topProject.getProjects(), bottomProject);//想等于Project进行合并。生成新的Project
final RelNode input = bottomProject.getInput();
if (RexUtil.isIdentity(newProjects, input.getRowType())) {//如果新生成的Project和底Project的输入input是否相等。
    if (force
            || input.getRowType().getFieldNames()
            .equals(topProject.getRowType().getFieldNames())) {//如果是强制模式,字段也相等,直接把底层Project子输入,注册到优化器
        call.transformTo(input);
        return;
    }
}
// replace the two projects with a combined projection
relBuilder.push(bottomProject.getInput());//使用了底层Project的子input push到构建器,想等于直接移除底层Project
relBuilder.project(newProjects, topProject.getRowType().getFieldNames());//使用合并的Project到构建器,做了合并Project变换
call.transformTo(relBuilder.build());

在最后,排除两者Project操作相同后,bottomProject.getInput()压入构建器内作为顶层Project操作的子RelNode,使用relBuilder.project方法把newProjects生成新的合并后的Project投影操作,注册到等价的关系表达式集合RelSet。

总结

HiveProjectMergeRule优化规则的优化功能相对还较简单的,把上下两个Project投影操作(RelNode操作符树来讲的上下关系),从Sql语句来说,把内外层两个Select进行合并一个Select的优化操作过程,本篇文章从原理和源码进行解析此规则是如何实现的。

0 人点赞