This is an automated email from the ASF dual-hosted git repository.
hyuan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/master by this push:
new 66ce217 Following [CALCITE-3916] Refine comments for top down
optimizer (Jinpeng Wang)
66ce217 is described below
commit 66ce217cc15e8af9685fcb25148bca65b3901a00
Author: jinpeng.wjp <[email protected]>
AuthorDate: Fri Jul 17 15:49:59 2020 +0800
Following [CALCITE-3916] Refine comments for top down optimizer (Jinpeng
Wang)
Close #2070
---
.../calcite/plan/volcano/TopDownRuleDriver.java | 108 +++++++++++++++++----
1 file changed, 91 insertions(+), 17 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/plan/volcano/TopDownRuleDriver.java
b/core/src/main/java/org/apache/calcite/plan/volcano/TopDownRuleDriver.java
index 0443159..c85c8e1 100644
--- a/core/src/main/java/org/apache/calcite/plan/volcano/TopDownRuleDriver.java
+++ b/core/src/main/java/org/apache/calcite/plan/volcano/TopDownRuleDriver.java
@@ -37,6 +37,15 @@ import java.util.Set;
import java.util.Stack;
import java.util.function.Predicate;
+/**
+ * A rule driver that apply rules in a Top-Down manner.
+ * By ensuring rule applying orders, there could be ways for
+ * space pruning and rule mutual exclusivity check.
+ *
+ * <p>This implementation use tasks to manage rule matches.
+ * A Task is a piece of work to be executed, it may apply some rules
+ * or schedule other tasks</p>
+ */
class TopDownRuleDriver implements RuleDriver {
private static final Logger LOGGER = CalciteTrace.getPlannerTaskTracer();
@@ -77,12 +86,24 @@ class TopDownRuleDriver implements RuleDriver {
@Override public void drive() {
TaskDescriptor description = new TaskDescriptor();
+
+ // Starting from the root's OptimizeGroup task.
tasks.push(new OptimizeGroup(planner.root, planner.infCost));
+
+ // ensure materialized view roots get explored.
+ // Note that implementation rules or enforcement rules are not applied
+ // unless the mv is matched
exploreMaterializationRoots();
- while (!tasks.isEmpty()) {
- Task task = tasks.pop();
- description.log(task);
- task.perform();
+
+ try {
+ // Iterates until the root is fully optimized
+ while (!tasks.isEmpty()) {
+ Task task = tasks.pop();
+ description.log(task);
+ task.perform();
+ }
+ } catch (VolcanoTimeoutException ex) {
+ LOGGER.warn("Volcano planning times out, cancels the subsequent
optimization.");
}
}
@@ -126,6 +147,9 @@ class TopDownRuleDriver implements RuleDriver {
}
public void onSetMerged(RelSet set) {
+ // When RelSets get merged, an optimized group may get extra opportunities.
+ // Clear the OPTIMISED state for the RelSubsets and all theirs ancestors
+ // so that they will be optimized again
applyGenerator(null, () -> clearProcessed(set));
}
@@ -146,17 +170,28 @@ class TopDownRuleDriver implements RuleDriver {
}
}
+ // a callback invoked when a RelNode is going to be added into a RelSubset,
+ // either by Register or Reregister. The task driver should need to schedule
+ // tasks for the new nodes.
public void onProduce(RelNode node, RelSubset subset) {
+
+ // if the RelNode is added to another RelSubset, just ignore it.
+ // It should be schedule in the later OptimizeGroup task
if (applying == null || subset.set
!= VolcanoPlanner.equivRoot(applying.group().set)) {
return;
}
+ // extra callback from each task
if (!applying.onProduce(node)) {
return;
}
if (!planner.isLogical(node)) {
+ // For a physical node, schedule tasks to optimize its inputs.
+ // The upper bound depends on all optimizing RelSubsets that this Rel
belongs to.
+ // If there are optimizing subsets that comes from the same RelSet,
+ // invoke the passThrough method to generate a candidate for that Subset.
RelSubset optimizingGroup = null;
boolean canPassThrough = node instanceof PhysicalNode
&& !passThroughCache.contains(node);
@@ -256,7 +291,8 @@ class TopDownRuleDriver implements RuleDriver {
}
/**
- * O_GROUP.
+ * Optimize a RelSubset.
+ * It schedule optimization tasks for RelNodes in the RelSet.
*/
private class OptimizeGroup implements Task {
private final RelSubset group;
@@ -274,7 +310,7 @@ class TopDownRuleDriver implements RuleDriver {
}
if (group.taskState != null && upperBound.isLe(group.upperBound)) {
- // this group failed to optimize before or it is a ring
+ // either this group failed to optimize before or it is a ring
return;
}
@@ -313,7 +349,9 @@ class TopDownRuleDriver implements RuleDriver {
}
/**
- * Mark the group optimized.
+ * Mark the RelSubset optimized.
+ * When GroupOptimized returns, the group is either fully
+ * optimized and has a winner or failed to optimized
*/
private static class GroupOptimized implements Task {
private final RelSubset group;
@@ -332,11 +370,13 @@ class TopDownRuleDriver implements RuleDriver {
}
/**
- * O_EXPR.
+ * Optimize a logical node, including exploring its input and applying rules
for it.
*/
private class OptimizeMExpr implements Task {
private final RelNode mExpr;
private final RelSubset group;
+
+ // when true, only apply transformation rules for mExpr
private final boolean explore;
OptimizeMExpr(RelNode mExpr,
@@ -364,8 +404,8 @@ class TopDownRuleDriver implements RuleDriver {
}
/**
- * Ensure ExploreInput are working on the correct input group since calcite
- * may merge sets.
+ * Ensure that ExploreInputs are working on the correct input group.
+ * Currently, a RelNode's input may change since calcite may merge RelSets.
*/
private class EnsureGroupExplored implements Task {
@@ -397,7 +437,7 @@ class TopDownRuleDriver implements RuleDriver {
}
/**
- * E_GROUP.
+ * Explore an input for a RelNode
*/
private class ExploreInput implements Task {
private final RelSubset group;
@@ -458,7 +498,7 @@ class TopDownRuleDriver implements RuleDriver {
}
/**
- * APPLY_RULE.
+ * Apply a rule match
*/
private class ApplyRule implements GeneratorTask {
private final VolcanoRuleMatch match;
@@ -488,7 +528,10 @@ class TopDownRuleDriver implements RuleDriver {
}
}
+ // Decide how to optimize a physical node.
private Task getOptimizeInputTask(RelNode rel, RelSubset group) {
+ // If the physical does not in current optimizing RelSubset, it firstly
tries to
+ // convert the physical node either by converter rule or traits pass
though.
if (!rel.getTraitSet().satisfies(group.getTraitSet())) {
RelNode passThroughRel = convert(rel, group);
if (passThroughRel == null) {
@@ -508,15 +551,20 @@ class TopDownRuleDriver implements RuleDriver {
break;
}
}
+ // If the inputs are all processed, only DeriveTrait is required.
if (!unProcess) {
return new DeriveTrait(rel, group);
}
+ // If part of the inputs are not optimized, schedule for the node an
OptimizeInput task,
+ // which tried to optimize the inputs first and derive traits for further
execution.
if (rel.getInputs().size() == 1) {
return new OptimizeInput1(rel, group);
}
return new OptimizeInputs(rel, group);
}
+ // Try converting the physical node to another trait sets
+ // either by converter rule or traits pass though.
private RelNode convert(RelNode rel, RelSubset group) {
if (!passThroughCache.contains(rel)) {
if (checkLowerBound(rel, group)) {
@@ -540,6 +588,7 @@ class TopDownRuleDriver implements RuleDriver {
return null;
}
+ // check whether a node's lower bound is less than a RelSubset's upper bound
private boolean checkLowerBound(RelNode rel, RelSubset group) {
RelOptCost upperBound = group.upperBound;
if (upperBound.isInfinite()) {
@@ -550,7 +599,8 @@ class TopDownRuleDriver implements RuleDriver {
}
/**
- * O_INPUT when there is only one input.
+ * A task that optimize input for physical nodes who has only one input.
+ * This task can be replaced by OptimizeInputs but simplify lots of logic.
*/
private class OptimizeInput1 implements Task {
@@ -562,7 +612,6 @@ class TopDownRuleDriver implements RuleDriver {
this.group = group;
}
-
@Override public void describe(TaskDescriptor desc) {
desc.item("mExpr", mExpr).item("upperBound", group.upperBound);
}
@@ -588,7 +637,10 @@ class TopDownRuleDriver implements RuleDriver {
}
/**
- * O_INPUT.
+ * Optimize a physical node's inputs.
+ * This task calculates a proper upper bound for the input and invoke
+ * the OptimizeGroup task. Group pruning mainly happens here when
+ * the upper bound for an input is less than the input's lower bound
*/
private class OptimizeInputs implements Task {
@@ -618,6 +670,7 @@ class TopDownRuleDriver implements RuleDriver {
@Override public void perform() {
RelOptCost bestCost = group.bestCost;
if (!bestCost.isInfinite()) {
+ // calculate the upper bound for inputs
if (bestCost.isLt(upperBound)) {
upperBound = bestCost;
upperForInput = planner.upperBoundForInputs(mExpr, upperBound);
@@ -648,7 +701,7 @@ class TopDownRuleDriver implements RuleDriver {
}
if (processingChild == 0) {
- // Apply enforcing rules
+ // derive traits after all inputs are optimized successfully
tasks.push(new DeriveTrait(mExpr, group));
}
@@ -664,10 +717,15 @@ class TopDownRuleDriver implements RuleDriver {
RelOptCost upper = upperForInput;
if (!upper.isInfinite()) {
+ // UB(one input)
+ // = UB(current subset) - Parent's NonCumulativeCost - LB(other
inputs)
+ // = UB(current subset) - Parent's NonCumulativeCost - LB(all
inputs) + LB(current input)
upper = upperForInput.minus(lowerBoundSum)
.plus(lowerBounds.get(processingChild));
}
if (input.taskState != null && upper.isLe(input.upperBound)) {
+ LOGGER.debug("Failed to optimize because of upper bound. LB = {}, UP
= {}",
+ lowerBoundSum, upperForInput);
return;
}
@@ -708,20 +766,28 @@ class TopDownRuleDriver implements RuleDriver {
@Override public void perform() {
if (input != parent.getInput(i)) {
+ // The input has chnaged. So reschedule the optimize task.
input = (RelSubset) parent.getInput(i);
tasks.push(this);
tasks.push(new OptimizeGroup(input, upper));
return;
}
+
+ // Optimize input completed. Update the context for other inputs
if (context == null) {
+ // If there is no other input, just return (no need to optimize other
inputs)
return;
}
+
RelOptCost winner = input.getWinnerCost();
if (winner == null) {
- // the input fail to optimize due to group pruning
+ // The input fail to optimize due to group pruning
+ // Then there's no need to optimize other inputs.
context.lowerBoundSum = planner.infCost;
return;
}
+
+ // Update the context.
if (context.lowerBoundSum != null && context.lowerBoundSum !=
planner.infCost) {
context.lowerBoundSum = context.
lowerBoundSum.minus(context.lowerBounds.get(i));
@@ -731,6 +797,9 @@ class TopDownRuleDriver implements RuleDriver {
}
}
+ /**
+ * Derive traits for already optimized physical nodes.
+ */
private class DeriveTrait implements GeneratorTask {
private final RelNode mExpr;
@@ -749,7 +818,12 @@ class TopDownRuleDriver implements RuleDriver {
return;
}
}
+
+ // In case some implementations use rules to convert between different
physical conventions.
+ // Note that this is deprecated and will be removed in the future.
tasks.push(new ApplyRules(mExpr, group, false));
+
+ // Derive traits from inputs
if (!passThroughCache.contains(mExpr)) {
applyGenerator(this, this::derive);
}