可能是全網(wǎng)最深度的 Apache Kylin 查詢剖析

本文已被 Apache Kylin 官方收錄,傳送門:https://kyligence.io/zh/resources/apache-kylin-query-analysis/?utm_source=wechat&utm_medium=social&utm_campaign=kylin


閱讀本文前,請(qǐng)先閱讀:


Apche Kylin 是 Hadoop 大數(shù)據(jù)平臺(tái)上的一個(gè)開源 OLAP 引擎。它采用多維立方體(Cube)預(yù)計(jì)算技術(shù),可以將某些場(chǎng)景下的大數(shù)據(jù) SQL 查詢速度提升到亞秒級(jí)別(相對(duì)于之前的分鐘乃至小時(shí)級(jí)別的查詢速度)。

由于其在 OLAP 領(lǐng)域出色的表現(xiàn),在國(guó)內(nèi)外積累了很多用戶。我們知道,當(dāng)用戶輸入一條 sql 提交給 Kylin 進(jìn)行查詢時(shí),該 sql 是面向事實(shí)表和維度表的,而不是面向 Cube 的。當(dāng)我看到這樣的描述時(shí),忍不住會(huì)想 Kylin 是怎么做到這一點(diǎn)的呢?相信很多使用 Kylin 的用戶也會(huì)有相同的疑問(wèn),但遺憾的是,目前不管是出版的書籍還是網(wǎng)上能搜到的資料,都沒(méi)有對(duì)這方面詳細(xì)的介紹,所以就有了這篇文章。

本文將以一個(gè)典型的例子和大家一起從源碼級(jí)別看看 Kylin 到底是怎么做到把對(duì)原始表的查詢轉(zhuǎn)換為對(duì) Cube 的查詢的。雖是源碼級(jí),但不會(huì)貼很多代碼,盡量做到以流程圖加描述的方式講清楚這個(gè)過(guò)程。

一、概覽

如上圖,sql text 到物理執(zhí)行計(jì)劃主要分幾個(gè)階段:

  1. sql text -> parsed SqlNode:使用 SqlParser 解析 SQL, 把 SQL 轉(zhuǎn)換成為 AST(抽象語(yǔ)法樹),用 SqlNode 來(lái)表示
  2. parsed SqlNode -> validated SqlNode:使用 SqlValidator 語(yǔ)法檢查,根據(jù) meta 的元數(shù)據(jù)信息進(jìn)行語(yǔ)法驗(yàn)證,驗(yàn)證之后還是用 SqlNode 表示 AST 語(yǔ)法樹
  3. validated SqlNode -> RelNode:使用 SqlToRelConverter 進(jìn)行語(yǔ)義分析,根據(jù) SqlNode 及元信息構(gòu)建 RelNode 樹,也就是最初版本的邏輯計(jì)劃(Logical Plan)
  4. RelNode -> optimized RelNode:使用 HepPlanner 應(yīng)用 calcite 內(nèi)置 rules 進(jìn)行優(yōu)化
  5. optimized RelNode -> OLAPRel:使用 VolcanoPlanner 應(yīng)用 Kylin 自定義的 OLAP 相關(guān) rules 到 HepPlanner 優(yōu)化得到的 RelNode 上,得到 OLAPRel,OLAPRel 還是邏輯執(zhí)行計(jì)劃。OLAP rules 如下:
  • OLAPToEnumerableConverterRule: RelNode -> OLAPToEnumerableConverter
  • OLAPFilterRule: LogicalFilter -> OLAPFilterRel
  • OLAPProjectRule: LogicalProject -> OLAPProjectRel
  • OLAPAggregateRule: LogicalAggregate -> OLAPAggregateRel
  • OLAPJoinRule: LogicalJoin -> OLAPJoinRel/OLAPFilterRel
  • OLAPLimitRule: Sort -> OLAPLimitRel
  • OLAPSortRule: Sort -> OLAPSortRel
  • OLAPUnionRule: Union -> OLAPUnionRel
  • OLAPValuesRule: LogicalValues -> OLAPValuesRel
  1. OLAPRel -> EnumerableRel:通過(guò) OLAPToEnumerableConverter#implement 將 OLAPRel 轉(zhuǎn)化為物理執(zhí)行計(jì)劃 EnumerableRel,這個(gè)過(guò)程中會(huì)遞歸調(diào)用各個(gè) OLAPRel 節(jié)點(diǎn)的 implementOLAP、implementRewrite 等方法,也是在這一步中計(jì)算要使用哪個(gè) Cube
  2. EnumerableRel -> java code:通過(guò)物理執(zhí)行計(jì)劃生成最終要執(zhí)行的 java code,java code 包含讀取數(shù)據(jù)、數(shù)據(jù)處理、計(jì)算結(jié)果

上例中生成的 java code 見(jiàn)下文

二、OLAPRel 生成物理執(zhí)行計(jì)劃

該過(guò)程主要封裝在 OLAPToEnumerableConverter#implement 中,主要流程如下:

implementOLAP、implementRewrite、implementEnumerable 為 OLAPRel 接口的方法,每個(gè) OLAPRel 實(shí)現(xiàn)類都要有自己的實(shí)現(xiàn),雖然各個(gè)實(shí)現(xiàn)不同,但可以進(jìn)行一些歸納:

void implementOLAP(OLAPImplementor implementor)

  • 生成或修改自身一些成員,會(huì)影響自身 implementRewrite 的行為
  • 修改 OLAPContext 的一些成員,會(huì)影響其他 OLAPRel implementOLAP 或 implementRewrite 或 生成物理節(jié)點(diǎn)、生成物理節(jié)點(diǎn)對(duì)應(yīng)的 java code

void implementRewrite(RewriteImplementor rewriter)

  • 會(huì)對(duì)算子、參數(shù)進(jìn)行改寫;這是把 sql 表達(dá)的查原始表(事實(shí)表、維度表)改為查 Cube 的關(guān)鍵
  • 雖然每個(gè) OLAPRel 子類都實(shí)現(xiàn)了該方法,但不是所有的子類都會(huì)真正的去做重寫
  • rewrite 行為受自身或 OLAPContext 記錄的上下文信息影響

EnumerableRel implementEnumerable(List<EnumerableRel> inputs)

  • 將自身轉(zhuǎn)換成 EnumerableRel,即邏輯節(jié)點(diǎn)轉(zhuǎn)為物理節(jié)點(diǎn)
  • EnumerableRel#implement方法返回的 Result 用來(lái)生成該物理節(jié)點(diǎn)對(duì)應(yīng)的 java code

我們以概覽中的 sql 來(lái)作為示例來(lái)對(duì)生成物理執(zhí)行計(jì)劃的過(guò)程進(jìn)行分析

三、遞歸調(diào)用各 OLAPRel#implementOLAP

3.1、OLAPTableScan#implementOLAP

我們對(duì)以下幾個(gè)被修改的實(shí)例進(jìn)一步說(shuō)明:

  • context.firstTableScan:在一個(gè) query 或 subQuery 中,如果包含 join,join 的 left side 要查的表就是 firstTableScan;如果 query 不包含 join,from 后面的表就是 firstTableScan
    • firstTableScan 會(huì)被當(dāng)做是 factTable,無(wú)論它事實(shí)上是不是
    • factTable 會(huì)影響后面的 realization 選擇

由于 firstTableScan 會(huì)被當(dāng)做是 factTable,與概覽中的 sql 同義的下面這條 sql 查詢時(shí)會(huì)報(bào) No realization found 的異常,這是因?yàn)?Kylin 很不智能的把 left table 作為 firstTableScan(及對(duì)應(yīng) factTable),但在 Kylin 中沒(méi)有用以 KYLIN_SALES 為事實(shí)表的 model/cube:

SELECT KYLIN_SALES.TRANS_ID, SUM(KYLIN_SALES.PRICE), COUNT(KYLIN_ACCOUNT.ACCOUNT_ID)
FROM KYLIN_ACCOUNT
  INNER JOIN KYLIN_SALES ON KYLIN_SALES.BUYER_ID = KYLIN_ACCOUNT.ACCOUNT_ID
WHERE KYLIN_SALES.LSTG_SITE_ID != 1000
GROUP BY KYLIN_SALES.TRANS_ID
ORDER BY TRANS_ID
LIMIT 10;

注①:為什么 OLAPTableScan 除了自身的 tableColumns 外,還會(huì)包含 metricColumns ?

  • 由于 OLAPTableScan 必定是整個(gè) plan(或者說(shuō)某個(gè) subquery )的葉子節(jié)點(diǎn),上層任何算子要操作的列只能由 OLAPTableScan 提供,如上層要把對(duì) factTable 某列做 count 轉(zhuǎn)化為對(duì) cube 對(duì)應(yīng) metrics(count 度量)做 SUM,那就必須要有這個(gè) metrics 列
  • 作為 OLAPTableScan 并不知曉上層需要哪些列或 metrics 列做怎么樣的轉(zhuǎn)換或重寫,所以需要把這個(gè)表對(duì)應(yīng)的 tableColumns 和 metricsColumns 全都提供出來(lái)
  • metricsColumns 確實(shí)會(huì)來(lái)自不同的 model 或 cube,不過(guò)這沒(méi)關(guān)系,后面會(huì)有一個(gè) realization 選擇的步驟,并不會(huì)導(dǎo)致 query 中的 aggs 某些來(lái)自 Cube A,另一些來(lái)自 Cube B 這種情況

metricsColumns 命名規(guī)則:

  • 如果是 COUNT,返回 _KY_COUNT_
  • 如果是 COUNT (DISTINCT KYLIN_SALES.TRANS_ID),返回_KY_COUNT_DISTINCT_1_3c0c94b7_TRANS_ID_
  • 其他,如 SUM(KYLIN_SALES.PRICE),返回 _KY_SUM_1_3c0c94b7_PRICE_

其中 1_3c0c94b7 是 KYLIN_SALES 的別名,別名的目的是為了防止出現(xiàn)計(jì)算的 SUM(KYLIN_.SALESPRICE)SUM(KYLIN_SALES.PRICE) 的 metricsColumn name 一樣的問(wèn)題

3.2、OLAPJoinRel#implementOLAP

我們對(duì)以下幾個(gè)被修改的實(shí)例進(jìn)一步說(shuō)明:

  • this.isTopJoin:樹結(jié)構(gòu)最上層的 join 是 top join,其 isTopJoin 成員才是 true
  • context.hasJoin:
    • 影響 OLAPProjectRel rewrite 行為,若 context.hasJoin 為 true 且 project 在最內(nèi)層 join 的內(nèi)部(context.afterJoin 為 false),則該 OLAPProjectRel 無(wú)需做 rewrite。這是因?yàn)?OLAPProjectRel#implementRewrite 主要是增加 projectList,增加的是維度做 agg 的度量列(如增加了 Count 的 metrics 列,OLAPAggregateRel 會(huì)對(duì)該列做 Sum 來(lái)替換對(duì)原始表相應(yīng)維度列的 Count),OLAPAggregateRel 會(huì)使用該新增的度量列進(jìn)行 aggregation 部分的 rewrite
    • 當(dāng)一個(gè) OLAPJoinRel 執(zhí)行 implementOLAP 方法時(shí),context.hasJoin 為 true,則說(shuō)明該 join 不是最頂層的 join

3.3、OLAPFilterRel#implementOLAP

  • context.allColumns、context.filterColumns 會(huì)影響之后的 cube 選擇
  • context.filter 會(huì)被用來(lái)過(guò)濾 cube 下的 segments 以及將該 filter 下推到查某個(gè) segment 的數(shù)據(jù)(會(huì)反應(yīng)在生成發(fā)送給 HBase Coprocessor 的代碼中)

3.4、OLAPProjectRel#implementOLAP

  • 如果 this.hasJoin && !this.afterJoin ,則 OLAPProjectRel 不會(huì)進(jìn)行 rewrite(visitChild 除外)。這是因?yàn)?OLAPProjectRel rewrite 干的事情主要是增加 projectList,增加的是對(duì)維度做 agg 的度量列,OLAPAggregateRel 使用該新增的度量列進(jìn)行 aggregation 部分的 rewrite(比如 OLAPProjectRel rewrite 增加了 Count 的 metrics 列,OLAPAggregateRel 會(huì)對(duì)該 metrics 列做 SUM 來(lái)替換對(duì)相應(yīng)維度列的 COUNT)
  • context.allColumns 將對(duì)最終的 realization 選擇產(chǎn)生影響

3.5、OLAPAggregateRel#implementOLAP

  • 計(jì)算 columnRowType 時(shí)為什么要對(duì) agg 列名做轉(zhuǎn)換?為了與 OLAPTableScan 提供的 metricsColumn 匹配上,以在之后把對(duì)源表的列 agg 操作轉(zhuǎn)換為對(duì) cube 的 metricsColumn 列做 agg
  • context.groupByColumns、context.aggregations、context.limitPrecedesAggr 會(huì)對(duì)之后的 realization 產(chǎn)生影響

僅支持最內(nèi)層的 agg 出現(xiàn) count distinct 的一個(gè)示例如下

SELECT COUNT(DISTINCT TID)
FROM (
  SELECT KYLIN_SALES.TRANS_ID AS TID, SUM(KYLIN_SALES.PRICE), COUNT(KYLIN_ACCOUNT.ACCOUNT_ID)
  FROM KYLIN_SALES
    INNER JOIN KYLIN_ACCOUNT ON KYLIN_SALES.BUYER_ID = KYLIN_ACCOUNT.ACCOUNT_ID
  WHERE KYLIN_SALES.LSTG_SITE_ID != 1000
  GROUP BY KYLIN_SALES.TRANS_ID
) a

報(bào)錯(cuò)[圖片上傳失敗...(image-2b7d64-1558959393134)]其實(shí)這里可以做個(gè)優(yōu)化,對(duì)于這種情況的外層 COUNT DISTINCT 其實(shí)可以先對(duì) subQuery 使用預(yù)計(jì)算

四、選擇 Realization

整個(gè)過(guò)程封裝在 RealizationChooser#``selectRealization 中,分為幾步來(lái)講

4.1、對(duì) model 及對(duì)應(yīng)的 realizations 進(jìn)行過(guò)濾及排序

  1. 獲取屬于該 project 下 factTableName 與查詢中事實(shí)表相等的所有 realizations,factTableName 即 context.firstTableScan.getTableName
  2. 對(duì) realizations 執(zhí)行過(guò)濾,得到 filteredRealizations
  3. NOT READY cube 會(huì)被過(guò)濾
  4. 黑名單中的 cube 會(huì)被過(guò)濾
  5. cube.allColumns 必須與 OLAPContext.allColumns 相等或是其父集
  6. cube.allColumns:事實(shí)表的外鍵列;維度表的主鍵列;所有度量涉及的列;所有維度列
  7. OLAPContext.allColumns:均在 OLAPRel#implementOLAP 方法中添加
  8. filterColumns 列,在 OLAPFilterRel#implementOLAP 中添加
  9. project 包含的列(即 agg 參數(shù)列即 group by 列),在 OLAPProjectRel#implementOLAP 添加
  10. 遍歷 filteredRealizations,對(duì)于每個(gè) realization,獲取其 model,并記錄每個(gè) mode 對(duì)應(yīng)的最小的 realization cost 及 model 對(duì)應(yīng)的 Set<IRealization>
  11. 根據(jù)各個(gè) model 對(duì)應(yīng)的最小 realization cost,對(duì)各個(gè) model -> ``Set<``IRealization``> 進(jìn)行排序,得到 modelMap: Map<DataModelDesc, Set<IRealization>>

如果 modelMap 為空,則拋 No model found for ... 異常

4.2、從 modelMap 中選擇最終的 realization

遍歷 modelMap: Map<DataModelDesc, Set<IRealization>> 每一個(gè) entry:

  1. IRealization realization = QueryRouter.selectRealization(context, entry.getValue())
  2. 若 realization 不為 null,則 realization 就是選中的 realization,設(shè)置為 context.realization,選擇過(guò)程結(jié)束;否則,continue,對(duì)下一個(gè) entry 進(jìn)行同樣的調(diào)用
  3. 若遍歷完所有的 entry,依然沒(méi)有符合要求的 realization,則拋異常 NoRealizationFoundException

IRealization selectRealization(OLAPContext olapContext, Set<IRealization> realizations) 邏輯如下:

  1. 對(duì)候選的 realizations 應(yīng)用 3 條規(guī)則,以進(jìn)行過(guò)濾和重新排序:
  2. 移除黑名單、被配置 kylin.query.realization-filter 過(guò)濾的
  3. 移除不適用的(邏輯封裝在 CubeCapabilityChecker#check 中),以下幾種情況不適用:
  4. OLAPContext 維度列(其 groupByColumns(在 OLAPAggregateRel#implementOLAP 中添加) + filterColumns(在 OLAPFilterRel#implementOLAP 中添加))中存在不在 cube 維度列中的情況
  5. OLAPContext aggregations(在 OLAPAggregateRel#implementOLAP 中添加) 中存在不在 cube aggregations 中的情況
  6. limit 在 agg 之前(使用 OLAPContext#limitPrecedesAggr 判斷,在 OLAPAggregateRel#implementOLAP 中進(jìn)行判斷),會(huì)導(dǎo)致 cube 的度量結(jié)果與查詢不一致
  7. 對(duì)剩下的進(jìn)行排序,優(yōu)先級(jí)最高、cost 最小的勝出

五、遞歸應(yīng)用 implementRewrite

5.1、OLAPAggregateRel#implementRewrite part1

如上,主要分兩步:

  1. 使用 realization 中的 metrics 的 agg 替換原有的 agg,要求 metrics 與原有的 agg 是對(duì)相同的列做相同的 agg 計(jì)算
  2. 根據(jù)第 1 步中選擇的 metrics 計(jì)算出 rewriteFields(并添加到 context.rewriteFields 中),會(huì)在 OLAPProjectRel#implementOLAP 和 OLAPAggregateRel#implementOLAP part2 中使用

5.2、OLAPProjectRel#implementRewrite

若 context.rewriteFields 不為空,則說(shuō)明后續(xù) OLAPAggregateRel#implementRewrite part2 會(huì)需要把對(duì)源表列的 agg 操作重寫為對(duì) cube metrics 列的 agg,這這里需要準(zhǔn)備好 OLAPAggregateRel#implementRewrite part2 需要的 metrics 列

5.3、OLAPAggregateRel#implementRewrite part2

下面流程圖按下標(biāo)遍歷 aggCalls 中的每個(gè)元素 aggCall,下標(biāo)為 i


把對(duì)源表列的 agg 操作重寫為對(duì) cube metrics 列的 agg,其中如果是 COUNT 操作,需要重寫為 SUM。需要注意的是,在這些 OLAPRel 中,columnRowType 各個(gè) col 主要是通過(guò)在 input.columnRowType 中的 index 來(lái)引用,而不是直接使用 name(當(dāng)然也會(huì)包含 name)

本例中:

  • SUM(KYLIN_SALES.PRICE) 重寫為 SUM(_KY_SUM_1_3c0c94b7_PRICE_)
    • PRICE 在 input.columnRowType 中 index 為 1
    • _KY_SUM_1_3c0c94b7_PRICE_ 在 input.columnRowType 中 index 為 4
  • COUNT(KYLIN_ACCOUNT.ACCOUNT_ID) 重寫為 SUM(_KY_COUNT__)
    • ACCOUNT_ID 在 input.columnRowType 中 index 2
      -_KY_COUNT__ 在 input.columnRowType 中 index 3

六、生成物理執(zhí)行計(jì)劃及 code gen

由于 Calcite 各個(gè)物理節(jié)點(diǎn)及 code gen 涉及代碼及模塊非常多,暫不在這里展開

每個(gè)EnumerableRel#implement 方法返回的 Result 都會(huì)生成一段 java code,parent EnumerableRel 生成的 java code 還會(huì)包含 child 生成的 java code,最終最頂層的 EnumerableRel 生成的 java code 就是完整的。

在 Kylin 中,OLAPJoinRel 對(duì)應(yīng)的物理節(jié)點(diǎn)還是其自身,當(dāng) OLAPJoinRel#implement 生成用于生成 java code 的 Result 時(shí),并不會(huì)使用到其 children,而是直接使用 OLAPContext.firstTableScan 作為事實(shí)表來(lái)獲取其對(duì)應(yīng)的 OLAPQuery 實(shí)例,如本例中的 join 生成的最終代碼如下

return ((org.apache.kylin.query.schema.OLAPTable) root.getRootSchema()
        .getSubSchema("DEFAULT").getTable("KYLIN_SALES")).executeOLAPQuery(root, 0);

事實(shí)上,雖然 OLAPJoinRel#implement 沒(méi)有直接使用 children 生成的代碼,但其 left OLAPTableScan#implement 得到的 Result 生成的代碼也是

return ((org.apache.kylin.query.schema.OLAPTable) root.getRootSchema()
        .getSubSchema("DEFAULT").getTable("KYLIN_SALES")).executeOLAPQuery(root, 0);

另外,OLAPToEnumerableConverter 也繼承了 EnumerableRel,實(shí)現(xiàn)了自己的 implement 物化方法,也就是觸發(fā)了本文中所有:

  • 自頂向下遞歸調(diào)用各個(gè) OLAPRel 節(jié)點(diǎn) implementOLAP 方法
  • realization 選擇
  • 自頂向下遞歸調(diào)用各個(gè) OLAPRel 節(jié)點(diǎn) implementRewrite 方法
  • 將各個(gè) OLAPRel 轉(zhuǎn)為 EnumerableRel
  • 自頂向下遞歸調(diào)用各個(gè) EnumerableRel 節(jié)點(diǎn) implement 方法得到用于生成 java code 的 Result

上述例子生成的 java 代碼如下:

// _inputEnumerable 為 OLAPQuery 類型,OLAPQuery
final org.apache.calcite.linq4j.Enumerable<java.lang.Object[]> _inputEnumerable = ((org.apache.kylin.query.schema.OLAPTable) root.getRootSchema().getSubSchema("DEFAULT").getTable("KYLIN_SALES")).executeOLAPQuery(root, 0);
final org.apache.calcite.linq4j.AbstractEnumerable child = new org.apache.calcite.linq4j.AbstractEnumerable(){
  public org.apache.calcite.linq4j.Enumerator<Object[]> enumerator() {
    return new org.apache.calcite.linq4j.Enumerator<Object[]>(){
        // 類型,OLAPQuery.enumerator() 得到的 inputEnumerator 為 OLAPEnumerator 類型
        // inputEnumerator 會(huì)調(diào)用 StorageEngine 去 HBase 中查詢指定 cube、指定 cuboid(及可能的 filter 下推)數(shù)據(jù)
        public final org.apache.calcite.linq4j.Enumerator<Object[]> inputEnumerator = _inputEnumerable.enumerator();
        public void reset() {
          inputEnumerator.reset();
        }

        public boolean moveNext() {
          while (inputEnumerator.moveNext()) {
            final Integer inp4_ = (Integer) ((Object[]) inputEnumerator.current())[4];
            if (inp4_ != null && inp4_.intValue() != 1000) {
              return true;
            }
          }
          return false;
        }

        public void close() {
          inputEnumerator.close();
        }

        public Object current() {
          final Object[] current = (Object[]) inputEnumerator.current();
          return new Object[] {
              current[0],
              current[5],
              current[13],
              current[11],
              current[10]};
        }
      
      };
  }
};

return child.groupBy(new org.apache.calcite.linq4j.function.Function1() {
    public Long apply(Object[] a0) {
      return (Long) a0[0];
    }
    public Object apply(Object a0) {
      return apply(
        (Object[]) a0);
    }
  }
  , new org.apache.calcite.linq4j.function.Function0() {
    public Object apply() {
      java.math.BigDecimal a0s0;
      boolean a0s1;
      a0s1 = false;
      a0s0 = new java.math.BigDecimal(0L);
      long a1s0;
      a1s0 = 0;
      Record3_0 record0;
      record0 = new Record3_0();
      record0.f0 = a0s0;
      record0.f1 = a0s1;
      record0.f2 = a1s0;
      return record0;
    }
  }
  , new org.apache.calcite.linq4j.function.Function2() {
    public Record3_0 apply(Record3_0 acc, Object[] in) {
      final java.math.BigDecimal inp4_ = in[4] == null ? (java.math.BigDecimal) null : org.apache.calcite.runtime.SqlFunctions.toBigDecimal(in[4]);
      if (inp4_ != null) {
        acc.f1 = true;
        acc.f0 = acc.f0.add(inp4_);
      }
      acc.f2 = acc.f2 + org.apache.calcite.runtime.SqlFunctions.toLong(in[3]);
      return acc;
    }
    public Record3_0 apply(Object acc, Object in) {
      return apply(
        (Record3_0) acc,
        (Object[]) in);
    }
  }
  , new org.apache.calcite.linq4j.function.Function2() {
    public Object[] apply(Long key, Record3_0 acc) {
      return new Object[] {
          key,
          acc.f1 ? acc.f0 : (java.math.BigDecimal) null,
          acc.f2};
    }
    public Object[] apply(Object key, Object acc) {
      return apply(
        (Long) key,
        (Record3_0) acc);
    }
  }
  ).orderBy(new org.apache.calcite.linq4j.function.Function1() {
    public Long apply(Object[] v) {
      return (Long) v[0];
    }
    public Object apply(Object v) {
      return apply(
        (Object[]) v);
    }
  }
  , org.apache.calcite.linq4j.function.Functions.nullsComparator(false, false)).take(10);

我們可以看到,整個(gè)計(jì)算過(guò)程迭代的讀取指定 cube、指定 cuboid 數(shù)據(jù),并執(zhí)行相應(yīng)的計(jì)算邏輯,是一個(gè)基于內(nèi)存的單機(jī)計(jì)算過(guò)程

七、支持/不支持 場(chǎng)景

7.1、支持

1、project list 中對(duì) group col、agg 做一些計(jì)算

SELECT KYLIN_SALES.TRANS_ID * 6, SUM(KYLIN_SALES.PRICE) + 1, COUNT(KYLIN_ACCOUNT.ACCOUNT_ID)
FROM KYLIN_SALES
  INNER JOIN KYLIN_ACCOUNT ON KYLIN_SALES.BUYER_ID = KYLIN_ACCOUNT.ACCOUNT_ID
WHERE KYLIN_SALES.LSTG_SITE_ID != 1000
GROUP BY KYLIN_SALES.TRANS_ID
ORDER BY TRANS_ID
LIMIT 10;

通過(guò)多加了一層 Project 來(lái)實(shí)現(xiàn)


2、count 參數(shù)不是直接的列

SELECT KYLIN_SALES.TRANS_ID, SUM(KYLIN_SALES.PRICE), COUNT(KYLIN_ACCOUNT.ACCOUNT_ID + 100)
FROM KYLIN_SALES
  INNER JOIN KYLIN_ACCOUNT ON KYLIN_SALES.BUYER_ID = KYLIN_ACCOUNT.ACCOUNT_ID
WHERE KYLIN_SALES.LSTG_SITE_ID != 1000
GROUP BY KYLIN_SALES.TRANS_ID
ORDER BY TRANS_ID
LIMIT 10;

7.2、不支持

1、非最內(nèi)層的 agg 包含 COUNT DISTINCT

SELECT COUNT(DISTINCT TID)
FROM (
  SELECT KYLIN_SALES.TRANS_ID AS TID, SUM(KYLIN_SALES.PRICE), COUNT(KYLIN_ACCOUNT.ACCOUNT_ID)
  FROM KYLIN_SALES
    INNER JOIN KYLIN_ACCOUNT ON KYLIN_SALES.BUYER_ID = KYLIN_ACCOUNT.ACCOUNT_ID
  WHERE KYLIN_SALES.LSTG_SITE_ID != 1000
  GROUP BY KYLIN_SALES.TRANS_ID
) a

報(bào)錯(cuò)



其實(shí)這里可以做個(gè)優(yōu)化,對(duì)于這種情況的外層 COUNT DISTINCT 其實(shí)可以先對(duì) subQuery 使用預(yù)計(jì)算

2、修改 agg 參數(shù)(count 除外)

SELECT KYLIN_SALES.TRANS_ID, SUM(KYLIN_SALES.PRICE + 100), COUNT(KYLIN_ACCOUNT.ACCOUNT_ID)
FROM KYLIN_SALES
  INNER JOIN KYLIN_ACCOUNT ON KYLIN_SALES.BUYER_ID = KYLIN_ACCOUNT.ACCOUNT_ID
WHERE KYLIN_SALES.LSTG_SITE_ID != 1000
GROUP BY KYLIN_SALES.TRANS_ID
ORDER BY TRANS_ID
LIMIT 10

報(bào)錯(cuò)


3、join 把維表作為左表

SELECT KYLIN_SALES.TRANS_ID, SUM(KYLIN_SALES.PRICE), COUNT(KYLIN_ACCOUNT.ACCOUNT_ID)
FROM KYLIN_ACCOUNT
  INNER JOIN KYLIN_SALES ON KYLIN_SALES.BUYER_ID = KYLIN_ACCOUNT.ACCOUNT_ID
WHERE KYLIN_SALES.LSTG_SITE_ID != 1000
GROUP BY KYLIN_SALES.TRANS_ID
ORDER BY TRANS_ID
LIMIT 10

報(bào)錯(cuò)


Kylin 機(jī)械的將 join 坐表作為 factTable

4、最內(nèi)層的 agg 內(nèi)還有 limit

SELECT SUM(KYLIN_SALES.PRICE) FROM KYLIN_SALES

查詢成功

SELECT SUM(PRICE) FROM (
    SELECT * FROM KYLIN_SALES LIMIT 1000
) A

報(bào)錯(cuò)


7.3、與 Calcite Materialized Views 比較

Kylin 是怎么做到 grouping 和 agg 補(bǔ)償?shù)??答:在?jì)算哪個(gè) cuboid 可滿足 query 的時(shí)候,會(huì)優(yōu)先根據(jù) grouping cols、agg cols、filter cols 來(lái)計(jì)算一個(gè) cuboid id:

  • 當(dāng)該 cuboid id 對(duì)應(yīng)的 cuboid 存在,則使用該 cuboid
  • 當(dāng)不存在,則會(huì)嘗試從已經(jīng)存在的 cuboids 中尋找一個(gè)最佳的替代 cuboid,具體過(guò)程封裝在 CuboidScheduler#findBestMatchCuboid 中,比如當(dāng) cuboid id 為 001000000000000100 的 cuboid 不存在,會(huì)使用 id 為 111111111111111111 的 cuboid

上述使用替代的 cuboid 與 grouping 補(bǔ)償和 agg 補(bǔ)償原理一致,均是通過(guò)更細(xì)粒度的 grouping 或 agg 來(lái)實(shí)現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容