可能是全網(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ǎng)上能搜到的資料鳞溉,都沒有對(duì)這方面詳細(xì)的介紹,所以就有了這篇文章鼠哥。

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

一、概覽

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

  1. sql text -> parsed SqlNode:使用 SqlParser 解析 SQL呆贿, 把 SQL 轉(zhuǎn)換成為 AST(抽象語法樹),用 SqlNode 來表示
  2. parsed SqlNode -> validated SqlNode:使用 SqlValidator 語法檢查,根據(jù) meta 的元數(shù)據(jù)信息進(jìn)行語法驗(yàn)證做入,驗(yàn)證之后還是用 SqlNode 表示 AST 語法樹
  3. validated SqlNode -> RelNode:使用 SqlToRelConverter 進(jìn)行語義分析冒晰,根據(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:通過 OLAPToEnumerableConverter#implement 將 OLAPRel 轉(zhuǎn)化為物理執(zhí)行計(jì)劃 EnumerableRel浪秘,這個(gè)過程中會(huì)遞歸調(diào)用各個(gè) OLAPRel 節(jié)點(diǎn)的 implementOLAP蒋情、implementRewrite 等方法,也是在這一步中計(jì)算要使用哪個(gè) Cube
  2. EnumerableRel -> java code:通過物理執(zhí)行計(jì)劃生成最終要執(zhí)行的 java code耸携,java code 包含讀取數(shù)據(jù)棵癣、數(shù)據(jù)處理、計(jì)算結(jié)果

上例中生成的 java code 見下文

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

該過程主要封裝在 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 用來生成該物理節(jié)點(diǎn)對(duì)應(yīng)的 java code

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

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

3.1哟绊、OLAPTableScan#implementOLAP

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

  • context.firstTableScan:在一個(gè) query 或 subQuery 中,如果包含 join痰憎,join 的 left side 要查的表就是 firstTableScan票髓;如果 query 不包含 join,from 后面的表就是 firstTableScan
    • firstTableScan 會(huì)被當(dāng)做是 factTable铣耘,無論它事實(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 中沒有用以 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(或者說某個(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 全都提供出來
  • metricsColumns 確實(shí)會(huì)來自不同的 model 或 cube朽缴,不過這沒關(guān)系善玫,后面會(huì)有一個(gè) realization 選擇的步驟水援,并不會(huì)導(dǎo)致 query 中的 aggs 某些來自 Cube A密强,另一些來自 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 一樣的問題

3.2薪鹦、OLAPJoinRel#implementOLAP

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

  • 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 無需做 rewrite。這是因?yàn)?OLAPProjectRel#implementRewrite 主要是增加 projectList楷兽,增加的是維度做 agg 的度量列(如增加了 Count 的 metrics 列地熄,OLAPAggregateRel 會(huì)對(duì)該列做 Sum 來替換對(duì)原始表相應(yīng)維度列的 Count),OLAPAggregateRel 會(huì)使用該新增的度量列進(jìn)行 aggregation 部分的 rewrite
    • 當(dāng)一個(gè) OLAPJoinRel 執(zhí)行 implementOLAP 方法時(shí)芯杀,context.hasJoin 為 true端考,則說明該 join 不是最頂層的 join

3.3、OLAPFilterRel#implementOLAP

  • context.allColumns揭厚、context.filterColumns 會(huì)影響之后的 cube 選擇
  • context.filter 會(huì)被用來過濾 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 來替換對(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è)過程封裝在 RealizationChooser#``selectRealization 中,分為幾步來講

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

  1. 獲取屬于該 project 下 factTableName 與查詢中事實(shí)表相等的所有 realizations巧还,factTableName 即 context.firstTableScan.getTableName
  2. 對(duì) realizations 執(zhí)行過濾鞭莽,得到 filteredRealizations
  3. NOT READY cube 會(huì)被過濾
  4. 黑名單中的 cube 會(huì)被過濾
  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边翁,選擇過程結(jié)束;否則硕盹,continue符匾,對(duì)下一個(gè) entry 進(jìn)行同樣的調(diào)用
  3. 若遍歷完所有的 entry,依然沒有符合要求的 realization莱睁,則拋異常 NoRealizationFoundException

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

  1. 對(duì)候選的 realizations 應(yīng)用 3 條規(guī)則待讳,以進(jìn)行過濾和重新排序:
  2. 移除黑名單、被配置 kylin.query.realization-filter 過濾的
  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 不為空,則說明后續(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 主要是通過在 input.columnRowType 中的 index 來引用抖仅,而不是直接使用 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í)表來獲取其對(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 沒有直接使用 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ì)算過程迭代的讀取指定 cube梯找、指定 cuboid 數(shù)據(jù)浦夷,并執(zhí)行相應(yīng)的計(jì)算邏輯获枝,是一個(gè)基于內(nèi)存的單機(jī)計(jì)算過程

七、支持/不支持 場(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;

通過多加了一層 Project 來實(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 來計(jì)算一個(gè) cuboid id:

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者逻恐。
  • 序言:七十年代末像吻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子复隆,更是在濱河造成了極大的恐慌拨匆,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挽拂,死亡現(xiàn)場(chǎng)離奇詭異惭每,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)亏栈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門台腥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人绒北,你說我怎么就攤上這事黎侈。” “怎么了镇饮?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵蜓竹,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我储藐,道長(zhǎng)俱济,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任钙勃,我火速辦了婚禮蛛碌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辖源。我一直安慰自己蔚携,他們只是感情好希太,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著酝蜒,像睡著了一般誊辉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上亡脑,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天堕澄,我揣著相機(jī)與錄音,去河邊找鬼霉咨。 笑死蛙紫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的途戒。 我是一名探鬼主播坑傅,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼喷斋!你這毒婦竟也來了唁毒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤继准,失蹤者是張志新(化名)和其女友劉穎枉证,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體移必,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年毡鉴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了崔泵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡猪瞬,死狀恐怖憎瘸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情陈瘦,我是刑警寧澤幌甘,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站痊项,受9級(jí)特大地震影響锅风,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鞍泉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一皱埠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧咖驮,春花似錦边器、人聲如沸训枢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恒界。三九已至,卻和暖如春砚嘴,著一層夾襖步出監(jiān)牢的瞬間仗处,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工枣宫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留婆誓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓也颤,卻偏偏與公主長(zhǎng)得像洋幻,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子翅娶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345