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

2022-04-25 15:39:41 浏览数 (1)

目录

背景

优化规则HiveFilterAggregateTransposeRule

  • matches方法逻辑详解
  • onMatch方法逻辑详解
  • canPush判断谓词表达式是否能下推的方法详解

总结

背景

这篇文章来讲优化规则HiveFilterAggregateTransposeRule,主要功能是将Filter过滤器下推到Aggregate聚合操作之下。满足的前提条件,这些谓词表达式必须是确定性的。

谓词下推,优化的思路大致为尽量地将过滤条件下推到离数据源近的位置。提前过滤掉减少数据量,减少不必要的IO。记录数和IO同时都是HiveCostModel成本模型的关键构成指标。但前提必须满足等价变换的大前提,所谓等价变换,就是相同的输入,返回相同的确定的结果,优化就是减少或降低中间过程的计算成本。

Fileter过滤器操作和Aggregate聚合操作调换顺序,也是谓词下推一种的优化规则。

优化规则HiveFilterAggregateTransposeRule

1)matches方法逻辑详解

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

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

代码语言:javascript复制
@Override
public boolean matches(RelOptRuleCall call) {
  final Filter filterRel = call.rel(0);
  RexNode condition = filterRel.getCondition();
  if (!HiveCalciteUtil.isDeterministic(condition)) {//判断是否为确定性方法,如果是确定性,并谓词表达式,否则跳出优化。
    return false;
  }
  return super.matches(call);
}

这里Hive实现了自己的判断,要求Filter操作的谓词条件,必须是确定性谓词表达式,否则退出优化。

表达式的确定性与非确定性区别:

一个表达式确定性与非确定性的区别是给定函数同一个确定值,是否永远返回同一个确定值。刚好相反的是非确定性函数,如随机函数Randow()每次返回的值都不确定。

关于onMatch方法优化规则HiveFilterAggregateTransposeRule没有实现,而是直接继承了Calcite实现优化规则父类FilterAggregateTransposeRule。

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优化规则的调用。

首先分别获取Filter和Aggregate对象,使用RelOptUtil.conjunctions把Filter对象谓词条件分解成有AND连接行表达式列表。获取Aggregate对象引用的字段列表,并判断与getGroupSet索引字段引用调整因子,以备下推Filter后AGG字段引用的调整使用。

代码语言:javascript复制
final Filter filterRel = call.rel(0);
final Aggregate aggRel = call.rel(1);
final List<RexNode> conditions =
        RelOptUtil.conjunctions(filterRel.getCondition());//把谓词条件分解成有AND连接行表达式列表
final RexBuilder rexBuilder = filterRel.getCluster().getRexBuilder();
final List<RelDataTypeField> origFields =
        aggRel.getRowType().getFieldList();//获取Aggregate对象引用的字段列表
final int[] adjustments = new int[origFields.size()];
int j = 0;
for (int i : aggRel.getGroupSet()) {//遍历GroupBy字段索引,并向前退
    adjustments[j] = i - j;
    j  ;
}

分离出哪些为可下推的谓词及其余不能下推的谓词列表。

首先conditions谓词列表,InputFinder访问遍历器生成表达式所用输入的位图,并使用bits返回描述表达式RelNode使用的输入的位集。canPush判断当前AGG对象中的,此谓词表达式元素是否可下推(canPush方法文章后面有讲解)。使用RelOptUtil.RexInputConverter遍历表达式树,根据调整因子adjustments转换RexInputRefs的索引并添加到可下推pushedConditions列表中,否则其余的谓词存放remainingConditions列表中。

代码语言:javascript复制
final List<RexNode> pushedConditions = Lists.newArrayList();//存放可下推的表达式列表
final List<RexNode> remainingConditions = Lists.newArrayList();//存放其余不可下推的表达式列表
  //InputFinder:Visitor which builds a bitmap of the inputs used by an expression.
  //bits方法:Returns a bit set describing the inputs used by an expression.
for (RexNode condition : conditions) {//遍历上述分解的,AND连接RexNode列表
    ImmutableBitSet rCols = RelOptUtil.InputFinder.bits(condition);
/**
 *  static class RelOptUtil.RexInputConverter
 * Walks an expression tree, converting the index of RexInputRefs based on some adjustment    factor.
 */
    if (canPush(aggRel, rCols)) {
        pushedConditions.add(
                condition.accept(
                        new RelOptUtil.RexInputConverter(rexBuilder, origFields,
                                aggRel.getInput(0).getRowType().getFieldList(),
                                adjustments)));
    } else {
        remainingConditions.add(condition);
    }
}

接下来,生成RelBuilder构建器对象,把谓词下推到AGG的子输入INPUT压入构建器,如果刚压入的带有下推谓词表达式的INPUT和原AGG的输入相同,则没有优化的必要,退出优化。复制AGG特征集合并使用已下推谓词的子输入RelNode生成新的RelNode对象,再补上剩余的没有下推的谓词条件,注册到RelSet等价关系表达式集合,以备优化器成本评估和选择,构建出最优的执行计划。

代码语言:javascript复制
final RelBuilder builder = call.builder();
RelNode rel = builder.push(aggRel.getInput()).filter(pushedConditions).build();//把谓词下推到AGG的子输入INPUT
if (rel == aggRel.getInput(0)) {//如果rel和原AGG的输入相同,退出优化。
   return;
}
rel = aggRel.copy(aggRel.getTraitSet(), ImmutableList.of(rel));//复制新生成的已下推的RelNode
rel = builder.push(rel).filter(remainingConditions).build();//再补上剩余的没有下推的谓词条件
call.transformTo(rel);//注册到优化器

3)canPush判断谓词表达式是否能下推的方法

如果Filter所引用的字段,没在GroupBy中使用,则不能下推。还有如果使用GroupSet语句,并在谓词表达式中出现的字段引用,都在grouping sets中出现,也是可以下推的。

代码语言:javascript复制
private boolean canPush(Aggregate aggregate, ImmutableBitSet rCols) {
    // If the filter references columns not in the group key, we cannot push
    final ImmutableBitSet groupKeys =
            ImmutableBitSet.range(0, aggregate.getGroupSet().cardinality());
    if (!groupKeys.contains(rCols)) {
        return false;
    }
    if (aggregate.indicator) {//标识是否AGG是否使用了GroupSets
      //在谓词表达式中出现的字段引用,都在grouping sets中出现,也是可以下推的。
        for (ImmutableBitSet groupingSet : aggregate.getGroupSets()) {
            if (!groupingSet.contains(rCols)) {
                return false;
            }
        }
    }
    return true;
}

总结

优化规则HiveFilterAggregateTransposeRule, Fileter过滤器操作和Aggregate聚合操作调换顺序,把谓词Filter过滤器下推到Aggregate聚合操作之下。提前过滤掉不必要的数据量,减少IO,达到优化效果。

0 人点赞