Impala關於ValueTransferGraph一段代碼的疑問解答

Impala關於ValueTransferGraph一段代碼的疑問解答

最近在Review IMPALA-9162patch 的時候,發現ValueTransferGraph有一處代碼不是很顯然:

  /**
   * Add value-transfer edges to 'g' based on the registered equi-join conjuncts.
   */
  private void constructValueTransfersFromEqPredicates(WritableGraph g) {
    for (ExprId id : globalState_.conjuncts.keySet()) {
      Expr e = globalState_.conjuncts.get(id);
      Pair<SlotId, SlotId> slotIds = BinaryPredicate.getEqSlots(e);
      if (slotIds == null) continue;

      TableRef sjTblRef = globalState_.sjClauseByConjunct.get(id);
      Preconditions.checkState(sjTblRef == null || sjTblRef.getJoinOp().isSemiJoin());
      boolean isAntiJoin = sjTblRef != null && sjTblRef.getJoinOp().isAntiJoin();

      TableRef ojTblRef = globalState_.ojClauseByConjunct.get(id);
      Preconditions.checkState(ojTblRef == null || ojTblRef.getJoinOp().isOuterJoin());
      if (ojTblRef == null && !isAntiJoin) {
        // this eq predicate doesn't involve any outer or anti join, ie, it is true for
        // each result row;
        // value transfer is not legal if the receiving slot is in an enclosed
        // scope of the source slot and the receiving slot's block has a limit
        Analyzer firstBlock = globalState_.blockBySlot.get(slotIds.first);
        Analyzer secondBlock = globalState_.blockBySlot.get(slotIds.second);
        if (LOG.isTraceEnabled()) {
          LOG.trace("Considering value transfer between " + slotIds.first.toString() +
              " and " + slotIds.second.toString());
        }
        if (!(secondBlock.hasLimitOffsetClause_ &&
            secondBlock.ancestors_.contains(firstBlock))) {
          g.addEdge(slotIds.first.asInt(), slotIds.second.asInt());
          if (LOG.isTraceEnabled()) {
            LOG.trace("value transfer: from " + slotIds.first.toString() + " to " +
                slotIds.second.toString());
          }
        }
        if (!(firstBlock.hasLimitOffsetClause_ &&
            firstBlock.ancestors_.contains(secondBlock))) {
          g.addEdge(slotIds.second.asInt(), slotIds.first.asInt());
          if (LOG.isTraceEnabled()) {
            LOG.trace("value transfer: from " + slotIds.second.toString() + " to " +
                    slotIds.first.toString());
          }
        }
        continue;
      }

這裏給 valueTransferGraph 添加邊之前的 if 判斷不是很顯然。這個函數用來構建 Analyzer 中的 valueTransferGraph,這是一個有向圖,圖中的點是 slot,圖中的邊是 slot 間的值傳遞關係。如果從 slotA 到 slotB 有一條邊,則表示 slotA 的所有約束都可以作用在 slotB 上。

比如這樣一個查詢:

select id, name from (
    select id, name from customer
  ) t
where id = name

這個查詢有兩個tuple descriptor,分別代表讀取 customer 表的結果和 InlineView t 的輸出結果。它們都有兩個slot,代表各自的兩個字段。Analyzer 會爲這個查詢生成如下的 valueTransferGraph,圖中4個節點代表4個slot,t.id 和 customer.id 都有邊指向對方,爲畫圖方便下面畫成了雙向的邊。同理 t.name 和 customer.name 也有邊指向對方。

t.id <--> customer.id
t.name <--> customer.name

有了這個圖後,再結合 t.id = t.name 這個謂詞,就可以推導出 customer.id = customer.name,表現出來就是做了謂詞下推。

代碼中的判斷是說,如果 slotB 所在的 query block 裏有 limit 或 offset 語句塊,並且 slotB 是在 slotA 查詢塊裏的子查詢出現,則不能添加 slotA -> slotB 這條邊。爲什麼 limit 或 offset 會影響謂詞下推呢?比如以下查詢就可以把外層查詢的where條件下推到子查詢裏:

select id, name, sum(account) from (
  select id, name, account from orders
  limit 100) t
where name = "Alice"
group by id, name

這個查詢本身的結果並不固定,因爲子查詢是從 orders 表抽 100 行數據,如果該表的數據較大,是多個 impalad 在做讀取的話,一般是執行較快的 impalad 返回的 100 行被採用。

然而,我們把謂詞 name = “Alice” 下推到子查詢裏也沒有錯:

select id, name, sum(account) from (
  select id, name, account from orders
  where name = "Alice"
  limit 100) t
where name = "Alice"
group by id, name

只不過是子查詢返回的結果更“精確”了,都不會被外層查詢的謂詞過濾掉。

有什麼例子是下推外層循環的謂詞後會出錯的呢?我後來想到了這樣一個例子:
假設 orders 表的 id 列是從0開始逐行連續遞增的,即是 0、1、2、3、…… 這樣的形式。假設 orders 表的數據量遠超過200行(如 id 至少到能排到 200)考慮以下查詢:

select id, name from (
  select id, name from orders
  order by id asc
  limit 100) t
where id % 2 = 0

子查詢 t 返回的行對應的 id 依次是 0、1、2、3、……、99. 在外層查詢裏被謂詞 “id % 2 = 0” 過濾後,只剩下 id 爲偶數的行,也就是 50 行。

如果我們下推謂詞到子查詢裏,變成這樣:

select id, name from (
  select id, name from orders
  where id % 2 = 0
  order by id asc
  limit 100) t
where id % 2 = 0

則子查詢返回的是100行偶數id的數據,這樣最終的結果就是 100 行!

同樣的可以舉一個子查詢包含 offset 語句塊的反例,因此子查詢裏有 limit/offset 語句塊時,外層查詢的謂詞是不一定能下推的。

在研究 IMPALA-9162 的時候,我發現如果子查詢裏有 outer join,且外層查詢的謂詞是作用在 nullable 那端的 slot 時,謂詞也是不能下推的,詳見我在 JIRA 裏的 comment: https://issues.apache.org/jira/browse/IMPALA-9162?focusedCommentId=16988532&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-16988532

發佈了17 篇原創文章 · 獲贊 7 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章