Calcite分析 -- TopDownRuleDriver

Calcite Version:1.26.0

Debug

TopDownOptTest, 加入

@Test void testMY() {
final String sql = "select mgr, count(*) from sales.emp\n"
+ "group by mgr order by mgr desc nulls last limit 5";
Query.create(sql).check();
}

RelOptTestBase.checkPlanning,会调用核心优化过程,

planner.setRoot(relBefore);
RelNode r = planner.findBestExp();

Graphviz,

StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw);
Dumpers.dumpGraphviz(this, pw);
static void dumpGraphviz(VolcanoPlanner planner, PrintWriter pw)

 

本身逻辑都是基于task栈,

所以只要了解有哪些task,和其中的状态机图,就了解其运行的方式,

  

执行的SQL,"select mgr, count(*) from sales.emp group by mgr order by mgr desc nulls last limit 5"

当前planner的情况,

4个RelSet,对应于上面SQL的4个算子,sort,aggre,project,tablescan

root是RelSubset#3,并且已经提前将convention转化为Enumerable,为了满足这个convention,所以给RelSet增加一个parent,AbstractConverter

  

从Root开始,

// Starting from the root's OptimizeGroup task.
tasks.push(new OptimizeGroup(planner.root, planner.infCost));

OptimizeGroup

优化什么?优化group是对于,group所在的Relset的Rels中的Relnode进行优化

 

区分逻辑算子和物理算子,

逻辑算子,非物理节点,并且convention不等于rootConvention;注意并不是名字叫logical就是逻辑,要看convention

 

针对,逻辑算子和物理算子所生成的task是不同的

看回这个例子,这时root所在的Relset有两个Relnode,

logicalSort是逻辑的

AbstractConverter是物理的,并且是enforcer,enforcer是要放在物理算子最后进行优化,我的理解这个选项就是为了converter

所以最终生成的tasks,注意这里OptimizeMExpr的explore是false

 

 

getOptimizeInputTask

在OptimizeGroup似乎,对所有的RelNodes都进行优化

但是对于物理算子,这里其实还是要匹配Traits的,可以看到如果traits不匹配,那么要先passthrough,也就是把Rel的input的traits进行convert后,生成新的passThroughRel,再去OptimizeInput

4种case,

- 如果rel和group的traits不相匹配,先convert生成pass through的Relnode

 - 如果convert失败,passThroughRel=null,那么不生成task

 - 如果成功继续,

 

 

- 如果rel的所有input已经完成优化,那么只需要生成DeriveTrait Task;因为OptimizeInput的目的就是继续优化input group

- 根据input的数目,选择相应的函数,

 

OptimizeInput1

对于物理算子,继续对input进行OptimizeGroup;

在OptimizeGroup前,先push两个task,也就是说,在OptimizeGroup任务结束后,需要检查以下两点

- CheckInput任务,mExpr的input是否改变,如果变了,需要重新调度优化task

- DeriveTrait任务,用到再说

所以这里是,

下一步继续优化,rel#15:RelSubset#3.NONE.[0 DESC-nulls-last]

这个和root同一个RelSet,所以包含的Relnodes是一样的,

所以也会生成对于logicalSort的优化,但是属于不同的group,

 

OptimizeMExpr

对于逻辑算子。只有对于逻辑算子才会applyRules;

这个task,目的就是递归topdown,在各个逻辑算子上进行ApplyRules

OptimizeMExpr-> ApplyRules-> ExploreInput-> OptimizeMExpr

(这里explore是false,由于这个OptimizeMExpr是从OptimizeGroup产生的)

 

在rel#15:RelSubset#3.NONE.[0 DESC-nulls-last]执行的结果,

 

ExploreInput

OptimizeGroup对等的作用,也是优化group,差别是这里只处理group.set.rels中的逻辑算子

还有一个很容易被忽略的细节,这里explore的值,

如果是通过OptimizeGroup生成的OptimizeMExpr,explore=false

而通过ExploreInput生成的OptimizeMExpr,explore=true,

explore会影响applyRules的时候的rule的筛选,为true,只会apply TransformationRule

 

并且在开始会判断,group.explore,这个逻辑这样写是不合理的,会产生误解, 应该写group.set.explore

因为实际是判断的是set的属性,set.exploringState != null

exploreInput实际也是explore set.rels,所以只要有其他的subset已经做了,就没有必要做了

 

结果如下,

  

按照这个一直Topdown下去,形成一系列的applyRules,

到此为止,经过的路径

Root -> AbstractConventer -> RelSubset#3.NONE -> LogicalSort.NONE -> RelSubset#2.NONE ...... -> LogicalTableScan.NONE

只是逻辑算子的展开,还没有对plan做任何优化

下面开始ApplyRule的过程,开始真正的优化

 

ApplyRules

逻辑比较简单,把matches中相应的rule加上,

结果,只加上一条rule,

 

EnumerableTableScanRule为啥没有加上?

EnumerableTableScanRule是ConverterRule,但不是TransformationRule

为何对于ConverterRule,outTrait==rootConvention,就不是TransformationRule?这个只是用于转换输出的Convention,放后面做?

 

筛选的时候,有TransformationRule,但是不满足,不会pop,还继续留在ruleQueue里面

  

ApplyRule

就是调用Rule的onMatch

 

例子,

AggregateProjectMergeRule

逻辑算子的变化rule

实际就是将Aggre和Project进行merge成一个新的Aggre,可以看到新的Aggre的input就是原先project的input

 

transformTo

Rule最后都会调用transformTo,

这里的逻辑,主要是处理Rule所新产生的Relnode

主要的逻辑是,ensureRegistered,但是这里equivRel是原先的RelNode, 可以重用RelSet,

结果,rule生成新的RelNode被加入原先的Relset中,作为等价的关系算子;并且对于RelSet#0,多了一个parent

 

ruleDriver.onProduce

Rule生成新的RelNode,会调用addRelToSet

这里会对应新增加的Node,增加新的Task

- 逻辑算子,增加OptimizeMExpr task,

 

 - 物理算子,增加OptimizeInput Task

有3种可能性,

- optimizingGroup不等于null,正常调用getOptimizeInputTask

  - RelNode本身的subset在优化中
  - pass through,但是pass through的subset和RelNode的traits相匹配 

- optimizingGroup等于null,对于passThrough subset,调用getOptimizeInputTask成功

  - addTask,这里task,可能是OptimizeInput,也可能是DeriveTrait

- optimizingGroup等于null,对于passThrough subset,调用getOptimizeInputTask返回null

  -啥都不做

这里passThrough的逻辑的case,参考Calcite分析 -- ConverterRule

  

EnsureGroupExplored

主要是设置group完成Explored

 

经过一系列的apply rules,所有针对rel#15:RelSubset#3.NONE.[0 DESC-nulls-last]的逻辑优化已经结束,

剩下的是一些EnumerableRule,包含在ruleQueue里面没有被pop的,因为之前的applyRules中的explore一直是true

其中EnumerableLimitRule(具体过程,参考Calcite分析 -- ConverterRule),会生成一个一个新的logicalSort,对后续优化有较大的影响

我理解原因是,在物理实现的时候,sort和limit是分开的,所以带limit的logicalSort需要拆成两个算子,

生成的新的sort算子,通过convert,生成带convention的subset,logicalSort的convention并不会变,仍然是None

基于Sort算子,生成EnumerableLimit算子,这个算子在注册的时候,会和旧的Sort算子注册在一个RelSet中,

这里注意EnumerableLimit的input是rel#21:RelSubset#4.ENUMERABLE.[0 DESC-nulls-last]

同时在增加物理算子,rel#22:EnumerableLimit.ENUMERABLE.[0 DESC-nulls-last](input=RelSubset#21,fetch=5)

会触发增加新的Task,

 

总结一下上面的流程,

逻辑优化Topdown展开,Root -> AbstractConventer -> RelSubset#3.NONE -> LogicalSort.NONE -> RelSubset#2.NONE ...... -> LogicalTableScan.NONE

逻辑算子Bottomup优化,各层apply transformtionRule,

物理优化的TopDown展开,BottomUp到RelSubset#3.NONE,开始apply Enumerable Rule, 

可以看下当前的matches里面,logical算子基本都还留一个EnumerableRule没有触发;

rel#14:LogicalSort.NONE,是因为已经触发EnumerableRule,生成了EnumerableLimit,所以size=0;rel#19:LogicalSort.NONE,刚生成,group还没优化

 

沿着EnumerableLimit的input,

OptimizeInput(rel#22) -> OptimizeGroup(rel#21) -> OptimizeMExpr -> ApplyRules -> Apply到上面size=5的logicalSort

其中有一个rule,是SortRemoveRule,过程,参考,Calcite分析 -- SortRemoveRule

SortRemove Rule中做了RelSet merge,这个过程中整个Relset#4和他的subset rel#21,在merge中被remove掉

并且这个操作会重新触发被merge到的RelSet,以及他的parent的RelSet的重新优化,直到Root

所以这里触发了重新优化Root的OptimizeGroup,其他的Group后面会连锁的被优化

OptimizeGroup(rel#16:RelSubset#3.ENUMERABLE.[0 DESC-nulls-last])-> OptimizeInput (rel#22:EnumerableLimit.ENUMERABLE) -> OptimizeGroup(rel#24:RelSubset#2.ENUMERABLE)

虽然开始的时候就调用过OptimizeGroup(rel#16:RelSubset#3.ENUMERABLE),但是现在多了rel#22:EnumerableLimit,所以触发到RelSubset#2.ENUMERABLE的优化

 

RelSubset#2.ENUMERABLE的优化,会产生3个OptimizeMExpr

但是当前RuleQueue里面只剩下EnumerableRule,没有逻辑优化rule可以触发,

所以OptimizeMExpr的作用,只能是apply MExpr所对应的EnumerableRule

触发完EnumerableSortedAggregateRule后,具体过程参考,Calcite分析 -- ConverterRule

结果如下,

由于EnumerableSortedAggregateRule,中的addConverter,PassThrough,生成两个新的物理算子

从而触发了两个OptimizeInput task

 

 

 

rel#29:RelSubset#0.ENUMERABLE.[3 DESC-nulls-last] 

从Task19 OptimizeInput触发到rel#29:RelSubset#0.ENUMERABLE.[3 DESC-nulls-last]的优化,

 

 

rel#29:RelSubset#0.ENUMERABLE.[3 DESC-nulls-last]优化触发EnumerableTableScanRule

生成一个物理算子和两个converter

之前在changeTraits时,结果只是增加一个subset,觉得好像没啥用,为啥不直接改RelNode的convention?

原来是增加的subset的required为true,后续在生成物理算子的时候,会触发conventer的生成

 

 

但在增加converter,调用addRelToSet的时候,触发driver.onProduce, 会走到pass through的逻辑,

比如对于,rel#33,

 

 

 

 

 

这里的passThroughTraits,大部分情况下返回null,

只有这些类实现了这个函数,

 

 

PassThrough的目的,是把required的traits,传递到RelNode的inputs,那么也不是什么算子都可以传递traits,

比如rel#33,是sort,那么input应该是没有经过sort的数据,所以这个traits是无法传递的;

比如EnumerableFilter,那么对于filter算子的input就应该保持traits一致。

所以这里PassThrough的RelNode的特点是,本身不会改变traits,所以才可以passThrough?

rel#34,也走到passThrough逻辑,不过34的traits本身就和rel#29:RelSubset#0.ENUMERABLE.[3 DESC-nulls-last]匹配,所以无需新生成passThroughRel,只是增加一个OptimizeInput

 

 

注册完converter,

继续加入物理算子,rel#31:EnumerableTableScan.ENUMERABLE.[](table=[CATALOG, SALES, EMP])

第一次有效的触发了subset的cost更新和propagate,参考Calcite分析 -- Cost

OptimizeInput(rel#34) -> OptimizeGroup(rel#32:RelSubset#0.ENUMERABLE.[]) 

到此rel#29:RelSubset#0.ENUMERABLE.[3 DESC-nulls-last]的优化完成.

 

OptimizeGroup(rel#24:RelSubset#2.ENUMERABLE -> OptimizeGroup(rel#29:RelSubset#0.ENUMERABLE.[3 DESC-nulls-last] -> OptimizeGroup(rel#32:RelSubset#0.ENUMERABLE.[]) 

 

继续优化,rel#24:RelSubset#2.ENUMERABLE

从Task18开始,

 

 

从Task18 OptimizeInput触发到rel#27:RelSubset#2.ENUMERABLE.[0]的优化

 

OptimizeGroup(rel#24:RelSubset#2.ENUMERABLE -> OptimizeGroup(rel#27:RelSubset#2.ENUMERABLE.[0] -> OptimizeGroup(rel#25:RelSubset#0.ENUMERABLE.[3]

 

 

 

在优化到rel#12,触发EnumerableSortedAggregateRule,参考前面的关于这个rule的分析,结果如下,

 

 

 有Pass through,会对其触发OptimizeInput,

 

OptimizeGroup(rel#27:RelSubset#2.ENUMERABLE.[0] -> OptimizeGroup(rel#37:RelSubset#1.ENUMERABLE.[0 DESC-nulls-last]

 

 

触发,EnumerableProjectRule

 

 

OptimizeInput,PassThroughRel

OptimizeGroup(rel#27:RelSubset#2.ENUMERABLE.[0] -> OptimizeGroup(rel#37:RelSubset#1.ENUMERABLE.[0 DESC-nulls-last] -> OptimizeGroup(rel#40:RelSubset#1.ENUMERABLE.[]

 

之后,一路到优化,到完成rel#24:RelSubset#2.ENUMERABLE

 

一路回退到Task13,OptimizeInput(rel#17:AbstractConverter.ENUMERABLE.[0 DESC-nulls-last](input=RelSubset#15,convention=ENUMERABLE,sort=[0 DESC-nulls-last]))

再次触发到rel#15的优化,

 

在DeriveTrait中触发,ExpandConversionRule

注释写的是,将AbstractConverter转换成一系列的converters,但是这里没有生成实际的converter

 

这样就完成rel#16:RelSubset#3.ENUMERABLE.[0 DESC-nulls-last]的优化

后面一路bottomup上去,直到完成所有节点的优化。

 

buildCheapestPlan

其实逻辑很简单,

从root,沿着input进行遍历,将subset,替换成best的RelNode

最终优化成3个算子,#47是converter

 

posted on 2021-09-08 11:17  fxjwind  阅读(462)  评论(0编辑  收藏  举报