fxjwind Calcite分析 - Volcano模型


參考,https://matt33.com/2019/03/17/apache-calcite-planner/

 

Volcano模型使用,分為下面幾個步驟,

//1. 初始化
VolcanoPlanner planner = new VolcanoPlanner();
//2.addRelTrait
planner.addRelTraitDef(ConventionTraitDef.INSTANCE);
planner.addRelTraitDef(RelDistributionTraitDef.INSTANCE);
//3.添加rule, logic to logic
planner.addRule(FilterJoinRule.FilterIntoJoinRule.FILTER_ON_JOIN);
planner.addRule(ReduceExpressionsRule.PROJECT_INSTANCE);

//4.添加ConverterRule, logic to physical
planner.addRule(EnumerableRules.ENUMERABLE_MERGE_JOIN_RULE);
planner.addRule(EnumerableRules.ENUMERABLE_SORT_RULE);
//5. setRoot 方法注冊相應的RelNode
planner.setRoot(relNode);
//6. find best plan
relNode = planner.findBestExp();

1和2 初始化

addRelTraitDef,就是把traitDef加到這個結構里面

  /**
   * Holds the currently registered RelTraitDefs.
   */
  private final List<RelTraitDef> traitDefs = new ArrayList<>();

 

3. 增加Rule

  public boolean addRule(RelOptRule rule) {
    //加到ruleSet
    final boolean added = ruleSet.add(rule);
    mapRuleDescription(rule);

    // Each of this rule's operands is an 'entry point' for a rule call.
    // Register each operand against all concrete sub-classes that could match
    // it.
    for (RelOptRuleOperand operand : rule.getOperands()) {
      for (Class<? extends RelNode> subClass
          : subClasses(operand.getMatchedClass())) {
        classOperands.put(subClass, operand);
      }
    }

    // If this is a converter rule, check that it operates on one of the
    // kinds of trait we are interested in, and if so, register the rule
    // with the trait.
    if (rule instanceof ConverterRule) {
      ConverterRule converterRule = (ConverterRule) rule;
      final RelTrait ruleTrait = converterRule.getInTrait();
      final RelTraitDef ruleTraitDef = ruleTrait.getTraitDef();
      if (traitDefs.contains(ruleTraitDef)) {
        ruleTraitDef.registerConverterRule(this, converterRule);
      }
    }
    return true;
  }

a. 更新classOperands

記錄Relnode和Rule的match關系,

multimap,一個relnode可以對應於多條rule的operand

  /**
   * Operands that apply to a given class of {@link RelNode}.
   *
   * <p>Any operand can be an 'entry point' to a rule call, when a RelNode is
   * registered which matches the operand. This map allows us to narrow down
   * operands based on the class of the RelNode.</p>
   */
  private final Multimap<Class<? extends RelNode>, RelOptRuleOperand>
      classOperands = LinkedListMultimap.create();

 

首先取出rule的operands,

operands包含所有rule中的operand,以flatten的方式

  /**
   * Flattened list of operands.
   */
  public final List<RelOptRuleOperand> operands;

  /**
   * Creates a flattened list of this operand and its descendants in prefix
   * order.
   *
   * @param rootOperand Root operand
   * @return Flattened list of operands
   */
  private List<RelOptRuleOperand> flattenOperands(
      RelOptRuleOperand rootOperand) {
    final List<RelOptRuleOperand> operandList = new ArrayList<>();

    // Flatten the operands into a list.
    rootOperand.setRule(this);
    rootOperand.setParent(null);
    rootOperand.ordinalInParent = 0;
    rootOperand.ordinalInRule = operandList.size();
    operandList.add(rootOperand);
    flattenRecurse(operandList, rootOperand);
    return ImmutableList.copyOf(operandList);
  }

  /**
   * Adds the operand and its descendants to the list in prefix order.
   *
   * @param operandList   Flattened list of operands
   * @param parentOperand Parent of this operand
   */
  private void flattenRecurse(
      List<RelOptRuleOperand> operandList,
      RelOptRuleOperand parentOperand) {
    int k = 0;
    for (RelOptRuleOperand operand : parentOperand.getChildOperands()) {
      operand.setRule(this);
      operand.setParent(parentOperand);
      operand.ordinalInParent = k++;
      operand.ordinalInRule = operandList.size();
      operandList.add(operand);
      flattenRecurse(operandList, operand);
    }
  }

 

subClasses(operand.getMatchedClass()

取到operand自身的class,比如join

  //classes保存plan中的RelNode的class
  private final Set<Class<? extends RelNode>> classes = new HashSet<>();  

  public Iterable<Class<? extends RelNode>> subClasses(
      final Class<? extends RelNode> clazz) {
    return Util.filter(classes, clazz::isAssignableFrom);
  }

所以這里的邏輯是,找出哪些Rule中operands的class和plan中的RelNode的class是可以匹配上的

把這個對應關系加到classOperands,有了這個關系,我們后面在遍歷的時候,就知道有哪些rule和這個RelNode可能會match上,縮小搜索空間

 

b. 將conversion rule注冊到RelTraitDef

 

5. SetRoot

  public void setRoot(RelNode rel) {
    // We're registered all the rules, and therefore RelNode classes,
    // we're interested in, and have not yet started calling metadata providers.
    // So now is a good time to tell the metadata layer what to expect.
    registerMetadataRels();

    this.root = registerImpl(rel, null);
    if (this.originalRoot == null) {
      this.originalRoot = rel;
    }

    // Making a node the root changes its importance.
    this.ruleQueue.recompute(this.root);
    ensureRootConverters();
  }

 

核心調用是 registerImpl

 

  /**
   * Registers a new expression <code>exp</code> and queues up rule matches.
   * If <code>set</code> is not null, makes the expression part of that
   * equivalence set. If an identical expression is already registered, we
   * don't need to register this one and nor should we queue up rule matches.
   *
   * @param rel relational expression to register. Must be either a
   *         {@link RelSubset}, or an unregistered {@link RelNode}
   * @param set set that rel belongs to, or <code>null</code>
   * @return the equivalence-set
   */
  private RelSubset registerImpl(
      RelNode rel,
      RelSet set) {
    //如果已經注冊過,直接合並返回
    if (rel instanceof RelSubset) {
      return registerSubset(set, (RelSubset) rel);
    }

    // Ensure that its sub-expressions are registered.
    // 1. 遞歸注冊
    rel = rel.onRegister(this);

    // 2. 記錄下該RelNode是由哪個Rule Call產生的
    if (ruleCallStack.isEmpty()) {
      provenanceMap.put(rel, Provenance.EMPTY);
    } else {
      final VolcanoRuleCall ruleCall = ruleCallStack.peek();
      provenanceMap.put(
          rel,
          new RuleProvenance(
              ruleCall.rule,
              ImmutableList.copyOf(ruleCall.rels),
              ruleCall.id));
    }

    // 3. 注冊RelNode樹的class和trait
    registerClass(rel);

    registerCount++;

    //4. 注冊RelNode到RelSet
    final int subsetBeforeCount = set.subsets.size();
    RelSubset subset = addRelToSet(rel, set);

    final RelNode xx = mapDigestToRel.put(key, rel);

    // 5. 更新importance
    if (rel == this.root) {
      ruleQueue.subsetImportances.put(
          subset,
          1.0); // root的importance固定為1
    }

    //把inputs也加入到RelSubset里面
    for (RelNode input : rel.getInputs()) {
      RelSubset childSubset = (RelSubset) input;
      childSubset.set.parents.add(rel);

      // 由於調整了RelSubset結構,重新計算importance
      ruleQueue.recompute(childSubset);
    }

    // 6. Fire rules
    fireRules(rel, true);

    // It's a new subset.
    if (set.subsets.size() > subsetBeforeCount) {
      fireRules(subset, true);
    }

    return subset;
  }

5.1 rel = rel.onRegister(this)

onRegister,目的就是遞歸的對RelNode樹上的每個節點調用registerImpl

取出RelNode的inputs,這里bottom up的,join的inputs就是left,right children

然后對於每個input,調用ensureRegistered

  public RelNode onRegister(RelOptPlanner planner) {
    List<RelNode> oldInputs = getInputs();
    List<RelNode> inputs = new ArrayList<>(oldInputs.size());
    for (final RelNode input : oldInputs) {
      RelNode e = planner.ensureRegistered(input, null);
      }
      inputs.add(e);
    }
    RelNode r = this;
    if (!Util.equalShallow(oldInputs, inputs)) {
      r = copy(getTraitSet(), inputs);
    }
    r.recomputeDigest();
    return r;
  }

ensureRegistered

  public RelSubset ensureRegistered(RelNode rel, RelNode equivRel) {
    final RelSubset subset = getSubset(rel);
    if (subset != null) {
      if (equivRel != null) {
        final RelSubset equivSubset = getSubset(equivRel);
        if (subset.set != equivSubset.set) {
          merge(equivSubset.set, subset.set);
        }
      }
      return subset;
    } else {
      return register(rel, equivRel);
    }
  }
  public RelSubset register(
      RelNode rel,
      RelNode equivRel) {
    final RelSet set;
    if (equivRel == null) {
      set = null;
    } else {
      set = getSet(equivRel);
    }

    //遞歸調用registerImpl
    final RelSubset subset = registerImpl(rel, set); 

    return subset;
  }

 

5.2 RuleProvenance

記錄下由那個Rule Call,產生這個RelNode;RuleCall可能為null

final Map<RelNode, Provenance> provenanceMap;

  /**
   * A RelNode that came via the firing of a rule.
   */
  static class RuleProvenance extends Provenance {
    final RelOptRule rule;
    final ImmutableList<RelNode> rels;
    final int callId;

    RuleProvenance(RelOptRule rule, ImmutableList<RelNode> rels, int callId) {
      this.rule = rule;
      this.rels = rels;
      this.callId = callId;
    }
  }

 

5.3 registerClass

將Relnode的Class和traits注冊到相應的機構中,記錄planner包含何種RelNode和Traits

private final Set<Class<? extends RelNode>> classes = new HashSet<>();  
private final Set<RelTrait> traits = new HashSet<>();

  public void registerClass(RelNode node) {
    final Class<? extends RelNode> clazz = node.getClass();
    if (classes.add(clazz)) {
      onNewClass(node);
    }
    for (RelTrait trait : node.getTraitSet()) {
      if (traits.add(trait)) {
        trait.register(this);
      }
    }
  }

 

5.4 addRelToSet

RelSubset subset = addRelToSet(rel, set);

  private RelSubset addRelToSet(RelNode rel, RelSet set) {
    RelSubset subset = set.add(rel);
    mapRel2Subset.put(rel, subset);

    // While a tree of RelNodes is being registered, sometimes nodes' costs
    // improve and the subset doesn't hear about it. You can end up with
    // a subset with a single rel of cost 99 which thinks its best cost is
    // 100. We think this happens because the back-links to parents are
    // not established. So, give the subset another change to figure out
    // its cost.
    final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
    subset.propagateCostImprovements(this, mq, rel, new HashSet<>());

    return subset;
  }

主要就是注冊RelNode所對應的RelSubset

注意這里是IdentityHashMap,所以比較的是RelNode的reference,而不是hashcode,不同的RelNode對象,就會對應各自不同的RelSubset

因為一個RelNode對象一定在一個RelSet中,但是不同的RelSet中可能包含相同RelNode對象實例,比如都有join對象

  /**
   * Map each registered expression ({@link RelNode}) to its equivalence set
   * ({@link RelSubset}).
   *
   * <p>We use an {@link IdentityHashMap} to simplify the process of merging
   * {@link RelSet} objects. Most {@link RelNode} objects are identified by
   * their digest, which involves the set that their child relational
   * expressions belong to. If those children belong to the same set, we have
   * to be careful, otherwise it gets incestuous.</p>
   */
  private final IdentityHashMap<RelNode, RelSubset> mapRel2Subset =
      new IdentityHashMap<>();

propagateCostImprovements,因為RelSet發生變化,可能產生新的best cost,所以把當前的change告訴其他的節點,更新cost,看看是否產生新的best cost

 

final RelNode xx = mapDigestToRel.put(key, rel);

表示這個RelNode,已經完成注冊,因為前面是通過這個Digest來判斷是否注冊過的

 

5.5 importance

importance用於表示RelSubset的優先級,優先級越高,越先進行優化

在RuleQueue里面,用這個結構來保存各個subset的importance

  /**
   * The importance of each subset.
   */
  final Map<RelSubset, Double> subsetImportances = new HashMap<>();

importance的計算方法如下,

Computes the importance of a node. Importance is defined as follows:

the root RelSubset has an importance of 1

 

其實很簡單,

比如root cost 3,兩個child的cost,2,5;而root的importance為1

那么兩個child的importance就是,0.2和0.5

所以越top的節點,importance越大,cost越大的節點,importance越大

 

5.6 fireRules

這里的fireRules,一般都是選擇DeferringRuleCall,所以不是馬上執行rule的,因為那樣比較低效,而是等真正需要的時候才去執行

  /**
   * Fires all rules matched by a relational expression.
   *
   * @param rel      Relational expression which has just been created (or maybe
   *                 from the queue)
   * @param deferred If true, each time a rule matches, just add an entry to
   *                 the queue.
   */
  void fireRules(
      RelNode rel,
      boolean deferred) {
    for (RelOptRuleOperand operand : classOperands.get(rel.getClass())) {
      if (operand.matches(rel)) {
        final VolcanoRuleCall ruleCall;
        if (deferred) {
          ruleCall = new DeferringRuleCall(this, operand);
        } else {
          ruleCall = new VolcanoRuleCall(this, operand);
        }
        ruleCall.match(rel);
      }
    }
  }

classOperands里面保存,每個RelNode所匹配到的所有的RuleOperand

classOperands只是說明當前Operands和RelNode匹配,但是當前RelNode子樹是否匹配Rule,需要進一步看

可以看到這里會Recurse的match,match的邏輯很長,這里就不看了

每匹配一次,solve+1,當solve == operands.size(),說明對整個Rule完成匹配

會調用onMatch

  /**
   * Applies this rule, with a given relational expression in the first slot.
   */
  void match(RelNode rel) {
    assert getOperand0().matches(rel) : "precondition";
    final int solve = 0;
    int operandOrdinal = getOperand0().solveOrder[solve];
    this.rels[operandOrdinal] = rel;
    matchRecurse(solve + 1);
  }

  /**
   * Recursively matches operands above a given solve order.
   *
   * @param solve Solve order of operand (&gt; 0 and &le; the operand count)
   */
  private void matchRecurse(int solve) {
    assert solve > 0;
    assert solve <= rule.operands.size();
    final List<RelOptRuleOperand> operands = getRule().operands;
    if (solve == operands.size()) {
      // We have matched all operands. Now ask the rule whether it
      // matches; this gives the rule chance to apply side-conditions.
      // If the side-conditions are satisfied, we have a match.
      if (getRule().matches(this)) {
        onMatch();
      }
    } else {......}}

對於DeferringRuleCall,

onMatch的邏輯,就是封裝成VolcanoRuleMatch,並丟到RuleQueue里面去

並沒有真正的執行Rule的onMatch,這就是Deferring

其實RuleQueue,RuleMatch, Importance 這些概念都是為了實現Deferring創造出來的,如果直接fire,機制就很簡單

    /**
     * Rather than invoking the rule (as the base method does), creates a
     * {@link VolcanoRuleMatch} which can be invoked later.
     */
    protected void onMatch() {
      final VolcanoRuleMatch match =
          new VolcanoRuleMatch(
              volcanoPlanner,
              getOperand0(),
              rels,
              nodeInputs);
      volcanoPlanner.ruleQueue.addMatch(match);
    }

 

6. findBestExp

  public RelNode findBestExp() {

    int cumulativeTicks = 0; //總步數,tick代表優化一次,觸發一個RuleMatch
    //這個for只會執行一次,因為只有Optimize phase里面加了RuleMatch,其他都是空的
    //RuleQueue.addMatch中,phaseRuleSet != ALL_RULES 會過濾到其他的phase
    for (VolcanoPlannerPhase phase : VolcanoPlannerPhase.values()) {
      setInitialImportance(); //初始化impoartance

      RelOptCost targetCost = costFactory.makeHugeCost(); //目標cost,設為Huge
      int tick = 0; //如果for執行一次,等同於cumulativeTicks
      int firstFiniteTick = -1; //第一次找到可執行plan用的tick數
      int giveUpTick = Integer.MAX_VALUE; //放棄優化的tick數

      while (true) {
        ++tick;  //開始一次優化,tick+1
        ++cumulativeTicks;
        if (root.bestCost.isLe(targetCost)) { //如果bestcost < targetCost,說明找到可執行的計划
          if (firstFiniteTick < 0) { //如果是第一次找到
            firstFiniteTick = cumulativeTicks; //更新firstFiniteTick

            clearImportanceBoost(); //清除ImportanceBoost,RelSubset中有個field,boolean boosted,表示是否被boost
          }
          if (ambitious) {
            // 會試圖找到更優的計划
            targetCost = root.bestCost.multiplyBy(0.9); //適當降低targetCost
 
            //如果impatient,需要設置giveUpTick
            //giveUpTick初始MAX_VALUE,當成功找到一個計划后,才會設置成相應的值
            if (impatient) { 
              if (firstFiniteTick < 10) { //如果第一次找到計划,步數小於10
                //下一輪如果25步找不到更優計划,放棄
                giveUpTick = cumulativeTicks + 25;
              } else {
                //如果計划比較復雜,步數放寬些
                giveUpTick =
                    cumulativeTicks
                        + Math.max(firstFiniteTick / 10, 25);
              }
            }
          } else {
            break; //非ambitious,有可用的計划就行,結束
          }
        } else if (cumulativeTicks > giveUpTick) { //放棄優化
          // We haven't made progress recently. Take the current best.
          break;
        } else if (root.bestCost.isInfinite() && ((tick % 10) == 0)) {
          //步數為整10,仍然沒有找到可用的計划
          //bestCost的初始值就是Infinite
          injectImportanceBoost(); //提高某些RelSubSet的Importance,加快cost降低
        }

        VolcanoRuleMatch match = ruleQueue.popMatch(phase); //從RuleQueue中找到importance最大的Match
        if (match == null) {
          break;
        }
        match.onMatch(); //觸發match

        // The root may have been merged with another
        // subset. Find the new root subset.
        root = canonize(root);
      }

      ruleQueue.phaseCompleted(phase);
    }

    RelNode cheapest = root.buildCheapestPlan(this);
    return cheapest;
  }

 

injectImportanceBoost

把僅僅包含Convention.NONE的RelSubSets的Importance提升,意思就讓這些RelSubsets先被優化

  /**
   * Finds RelSubsets in the plan that contain only rels of
   * {@link Convention#NONE} and boosts their importance by 25%.
   */
  private void injectImportanceBoost() {
    final Set<RelSubset> requireBoost = new HashSet<>();

  SUBSET_LOOP:
    for (RelSubset subset : ruleQueue.subsetImportances.keySet()) {
      for (RelNode rel : subset.getRels()) {
        if (rel.getConvention() != Convention.NONE) {
          continue SUBSET_LOOP;
        }
      }

      requireBoost.add(subset);
    }

    ruleQueue.boostImportance(requireBoost, 1.25);
  }

Convention.NONE,都是infinite cost,所以先優化他們會更有效的降低cost

public interface Convention extends RelTrait {
  /**
   * Convention that for a relational expression that does not support any
   * convention. It is not implementable, and has to be transformed to
   * something else in order to be implemented.
   *
   * <p>Relational expressions generally start off in this form.</p>
   *
   * <p>Such expressions always have infinite cost.</p>
   */
  Convention NONE = new Impl("NONE", RelNode.class);

 

PopMatch

找出importance最大的match,並且返回

 /**
   * Removes the rule match with the highest importance, and returns it.
   *
   * <p>Returns {@code null} if there are no more matches.</p>
   *
   * <p>Note that the VolcanoPlanner may still decide to reject rule matches
   * which have become invalid, say if one of their operands belongs to an
   * obsolete set or has importance=0.
   *
   * @throws java.lang.AssertionError if this method is called with a phase
   *                              previously marked as completed via
   *                              {@link #phaseCompleted(VolcanoPlannerPhase)}.
   */
  VolcanoRuleMatch popMatch(VolcanoPlannerPhase phase) {
    PhaseMatchList phaseMatchList = matchListMap.get(phase);

    final List<VolcanoRuleMatch> matchList = phaseMatchList.list;
    VolcanoRuleMatch match;
    for (;;) {
      if (matchList.isEmpty()) {
        return null;
      }
      if (LOGGER.isTraceEnabled()) {
        //...
      } else {
        match = null;
        int bestPos = -1;
        int i = -1;
        //找出importance最大的match
        for (VolcanoRuleMatch match2 : matchList) {
          ++i;
          if (match == null
              || MATCH_COMPARATOR.compare(match2, match) < 0) {
            bestPos = i;
            match = match2;
          }
        }
        match = matchList.remove(bestPos);
      }

      if (skipMatch(match)) {
        LOGGER.debug("Skip match: {}", match);
      } else {
        break;
      }
    }

    // A rule match's digest is composed of the operand RelNodes' digests,
    // which may have changed if sets have merged since the rule match was
    // enqueued.
    match.recomputeDigest();
    phaseMatchList.matchMap.remove(
        planner.getSubset(match.rels[0]), match);

    return match;
  }

 

onMatch

   /**
   * Called when all operands have matched.
   */
  protected void onMatch() {
    volcanoPlanner.ruleCallStack.push(this);
    try {
    getRule().onMatch(this);
    } finally {
    volcanoPlanner.ruleCallStack.pop();
    }
  }

ruleCallStack記錄當前在執行的RuleCall

最終調用到具體Rule的onMatch函數,做具體的轉換

 

buildCheapestPlan

  /**
   * Recursively builds a tree consisting of the cheapest plan at each node.
   */
  RelNode buildCheapestPlan(VolcanoPlanner planner) {
    CheapestPlanReplacer replacer = new CheapestPlanReplacer(planner);
    final RelNode cheapest = replacer.visit(this, -1, null);

    return cheapest;
  }

可以看到邏輯其實比較簡單,就是遍歷RelSubSet樹,然后從上到下都選best的RelNode形成新的樹

  /**
   * Visitor which walks over a tree of {@link RelSet}s, replacing each node
   * with the cheapest implementation of the expression.
   */
  static class CheapestPlanReplacer {
    VolcanoPlanner planner;

    CheapestPlanReplacer(VolcanoPlanner planner) {
      super();
      this.planner = planner;
    }

    public RelNode visit(
        RelNode p,
        int ordinal,
        RelNode parent) {
      if (p instanceof RelSubset) {
        RelSubset subset = (RelSubset) p;
        RelNode cheapest = subset.best; //取出SubSet中的best
        p = cheapest; //替換
      }

      List<RelNode> oldInputs = p.getInputs();
      List<RelNode> inputs = new ArrayList<>();
      for (int i = 0; i < oldInputs.size(); i++) {
        RelNode oldInput = oldInputs.get(i);
        RelNode input = visit(oldInput, i, p); //遞歸執行visit
        inputs.add(input); //新的input
      }
      if (!inputs.equals(oldInputs)) {
        final RelNode pOld = p;
        p = p.copy(p.getTraitSet(), inputs); //生成新的p
        planner.provenanceMap.put(
            p, new VolcanoPlanner.DirectProvenance(pOld));
      }
      return p;
    }
  }
}

 

BestCost是如何變化的?

每個RelSubSet都會記錄,

bestCost和bestPlan

  /**
   * cost of best known plan (it may have improved since)
   */
  RelOptCost bestCost;

  /**
   * The set this subset belongs to.
   */
  final RelSet set;

  /**
   * best known plan
   */
  RelNode best;

 

初始化

首先RelSubSet初始化的時候,會執行computeBestCost

  private void computeBestCost(RelOptPlanner planner) {
    bestCost = planner.getCostFactory().makeInfiniteCost(); //bestCost初始化成,Double.POSITIVE_INFINITY
    final RelMetadataQuery mq = getCluster().getMetadataQuery();
    for (RelNode rel : getRels()) {
      final RelOptCost cost = planner.getCost(rel, mq);
      if (cost.isLt(bestCost)) {
        bestCost = cost;
        best = rel;
      }
    }
  }

getCost

  public RelOptCost getCost(RelNode rel, RelMetadataQuery mq) {
    if (rel instanceof RelSubset) {
      return ((RelSubset) rel).bestCost; //如果是RelSubSet直接返回結果,因為動態規划,重用之前的結果,不用反復算
    }
    if (noneConventionHasInfiniteCost //Convention.NONE的cost為InfiniteCost,返回
        && rel.getTraitSet().getTrait(ConventionTraitDef.INSTANCE) == Convention.NONE) {
      return costFactory.makeInfiniteCost();
    }
    RelOptCost cost = mq.getNonCumulativeCost(rel); //算cost
    if (!zeroCost.isLt(cost)) {
      // cost must be positive, so nudge it
      cost = costFactory.makeTinyCost(); //如果算出負的cost,用TinyCost替代,1.0
    }
    for (RelNode input : rel.getInputs()) {
      cost = cost.plus(getCost(input, mq)); //遞歸把整個數的cost都加到root
    }
    return cost;
  }

 

getNonCumulativeCost

    /**
     * Estimates the cost of executing a relational expression, not counting the
     * cost of its inputs. (However, the non-cumulative cost is still usually
     * dependent on the row counts of the inputs.) The default implementation
     * for this query asks the rel itself via {@link RelNode#computeSelfCost},
     * but metadata providers can override this with their own cost models.
     *
     * @return estimated cost, or null if no reliable estimate can be
     * determined
     */
    RelOptCost getNonCumulativeCost();

    /** Handler API. */
    interface Handler extends MetadataHandler<NonCumulativeCost> {
      RelOptCost getNonCumulativeCost(RelNode r, RelMetadataQuery mq);
    }

 

getNonCumulativeCost最終調用的是RelNode#computeSelfCost

這是個抽象接口,每個RelNode的實現不同,看下比較簡單的Filter的實現,

  @Override public RelOptCost computeSelfCost(RelOptPlanner planner,
      RelMetadataQuery mq) {
    double dRows = mq.getRowCount(this);
    double dCpu = mq.getRowCount(getInput());
    double dIo = 0;
    return planner.getCostFactory().makeCost(dRows, dCpu, dIo);
  }

這里的實現就是單純用rowCount來表示cost

makeCost也是直接封裝成VolcanoCost對象

 

節點變更

各個地方當產生新的RelNode時,會調用Register,ensureRegistered,或registerImpl進行注冊

   public RelSubset register(
      RelNode rel,
      RelNode equivRel) {
    final RelSet set;
    if (equivRel == null) {
      set = null;
    } else {
      set = getSet(equivRel);
    }
    final RelSubset subset = registerImpl(rel, set);

    return subset;
  }

 

  public RelSubset ensureRegistered(RelNode rel, RelNode equivRel) {
    final RelSubset subset = getSubset(rel);
    if (subset != null) {
      if (equivRel != null) {
        final RelSubset equivSubset = getSubset(equivRel);
        if (subset.set != equivSubset.set) {
          merge(equivSubset.set, subset.set);
        }
      }
      return subset;
    } else {
      return register(rel, equivRel);
    }
  }

 

registerImpl調用addRelToSet,registerImpl的實現前面有

  private RelSubset addRelToSet(RelNode rel, RelSet set) {
    RelSubset subset = set.add(rel);
    mapRel2Subset.put(rel, subset);

    // While a tree of RelNodes is being registered, sometimes nodes' costs
    // improve and the subset doesn't hear about it. You can end up with
    // a subset with a single rel of cost 99 which thinks its best cost is
    // 100. We think this happens because the back-links to parents are
    // not established. So, give the subset another change to figure out
    // its cost.
    final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
    subset.propagateCostImprovements(this, mq, rel, new HashSet<>());

    return subset;
  }

propagateCostImprovements

  /**
   * Checks whether a relexp has made its subset cheaper, and if it so,
   * recursively checks whether that subset's parents have gotten cheaper.
   *
   * @param planner   Planner
   * @param mq        Metadata query
   * @param rel       Relational expression whose cost has improved
   * @param activeSet Set of active subsets, for cycle detection
   */
  void propagateCostImprovements(VolcanoPlanner planner, RelMetadataQuery mq,
      RelNode rel, Set<RelSubset> activeSet) {
    for (RelSubset subset : set.subsets) {
      if (rel.getTraitSet().satisfies(subset.traitSet)) {
        subset.propagateCostImprovements0(planner, mq, rel, activeSet);
      }
    }
  }

 

  void propagateCostImprovements0(VolcanoPlanner planner, RelMetadataQuery mq,
      RelNode rel, Set<RelSubset> activeSet) {
    ++timestamp;

    if (!activeSet.add(this)) { //檢測到環
      // This subset is already in the chain being propagated to. This
      // means that the graph is cyclic, and therefore the cost of this
      // relational expression - not this subset - must be infinite.
      LOGGER.trace("cyclic: {}", this);
      return;
    }
    try {
      final RelOptCost cost = planner.getCost(rel, mq); //獲取cost
      if (cost.isLt(bestCost)) {
        bestCost = cost;
        best = rel;

        // Lower cost means lower importance. Other nodes will change
        // too, but we'll get to them later.
        planner.ruleQueue.recompute(this); //cost變了,所以importance要重新算
        //遞歸的執行propagateCostImprovements
        for (RelNode parent : getParents()) {
          final RelSubset parentSubset = planner.getSubset(parent);
          parentSubset.propagateCostImprovements(planner, mq, parent,
              activeSet);
        }
        planner.checkForSatisfiedConverters(set, rel);
      }
    } finally {
      activeSet.remove(this);
    }
  }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM