Spark SQL Catalyst優化器

記錄一下個人對sparkSql的catalyst這個函數式的可擴展的查詢優化器的理解,目錄如下,

0. Overview
1. Catalyst工作流程
2. Parser模塊
3. Analyzer模塊
4. Optimizer模塊
5. SparkPlanner模塊
6. Job UI
7. Reference

Overview

Spark SQL的核心是Catalyst優化器,是以一種新穎的方式利用Scala的的模式匹配和quasiquotes機制來構建的可擴展查詢優化器。

sparkSql pipeline

sparkSql的catalyst優化器是整個sparkSql pipeline的中間核心部分,其執行策略主要兩方向,

  1. 基于規則優化/Rule Based Optimizer/RBO
    • 一種經驗式、啟發式優化思路
    • 對于核心優化算子join有點力不從心,如兩張表執行join,到底使用broadcaseHashJoin還是sortMergeJoin,目前sparkSql是通過手工設定參數來確定的,如果一個表的數據量小于某個閾值(默認10M?)就使用broadcastHashJoin
      • nestedLoopsJoin,P,Q雙表兩個大循環, O(M*N)
      • sortMergeJoin是P,Q雙表排序后互相游標
      • broadcastHashJoin,PQ雙表中小表放入內存hash表,大表遍歷O(1)方式取小表內容
  2. 基于代價優化/Cost Based Optimizer/CBO
    • 針對每個join評估當前兩張表使用每種join策略的代價,根據代價估算確定一種代價最小的方案
    • 不同physical plans輸入到代價模型(目前是統計),調整join順序,減少中間shuffle數據集大小,達到最優輸出

Catalyst工作流程

  • Parser,利用ANTLR將sparkSql字符串解析為抽象語法樹AST,稱為unresolved logical plan/ULP
  • Analyzer,借助于數據元數據catalog將ULP解析為logical plan/LP
  • Optimizer,根據各種RBO,CBO優化策略得到optimized logical plan/OLP,主要是對Logical Plan進行剪枝,合并等操作,進而刪除掉一些無用計算,或對一些計算的多個步驟進行合并

other

Optimizer是catalyst工作最后階段了,后面生成physical plan以及執行,主要是由sparkSql來完成。

  • SparkPlanner
    • 優化后的邏輯執行計劃OLP依然是邏輯的,并不能被spark系統理解,此時需要將OLP轉換成physical plan
    • 從邏輯計劃/OLP生成一個或多個物理執行計劃,基于成本模型cost model從中選擇一個
  • Code generation
    • 生成Java bytecode然后在每一臺機器上執行,形成RDD graph/DAG

Parser模塊

將sparkSql字符串切分成一個一個token,再根據一定語義規則解析為一個抽象語法樹/AST。Parser模塊目前基本都使用第三方類庫ANTLR來實現,比如Hive,presto,sparkSql等。

parser切詞

Spark 1.x版本使用的是Scala原生的Parser Combinator構建詞法和語法分析器,而Spark 2.x版本使用的是第三方語法解析器工具ANTLR4。

Spark2.x SQL語句的解析采用的是ANTLR4,ANTLR4根據語法文件SqlBase.g4自動解析生成兩個Java類:詞法解析器SqlBaseLexer和語法解析器SqlBaseParser。

SqlBaseLexer和SqlBaseParser都是使用ANTLR4自動生成的Java類。使用這兩個解析器將SQL字符串語句解析成了ANTLR4的ParseTree語法樹結構。然后在parsePlan過程中,使用AstBuilder.scala將ParseTree轉換成catalyst表達式邏輯計劃LogicalPlan。


Analyzer模塊

通過解析后ULP有了基本骨架,但是系統對表的字段信息是不知道的。如sum,select,join,where還有score,people都表示什么含義,此時需要基本的元數據信息schema catalog來表達這些token。最重要的元數據信息就是,

  • 表的schema信息,主要包括表的基本定義(表名、列名、數據類型)、表的數據格式(json、text、parquet、壓縮格式等)、表的物理位置
  • 基本函數信息,主要是指類信息

Analyzer會再次遍歷整個AST,對樹上的每個節點進行數據類型綁定以及函數綁定,比如people詞素會根據元數據表信息解析為包含age、id以及name三列的表,people.age會被解析為數據類型為int的變量,sum會被解析為特定的聚合函數,

詞義注入
//org.apache.spark.sql.catalyst.analysis.Analyzer.scala
  lazy val batches: Seq[Batch] = Seq( //不同Batch代表不同的解析策略
    Batch("Substitution", fixedPoint,
      CTESubstitution,
      WindowsSubstitution,
      EliminateUnions,
      new SubstituteUnresolvedOrdinals(conf)),
    Batch("Resolution", fixedPoint,
      ResolveTableValuedFunctions ::
      ResolveRelations ::  //通過catalog解析表或列基本數據類型,命名等信息
      ResolveReferences :: //解析從子節點的操作生成的屬性,一般是別名引起的,比如people.age
      ResolveCreateNamedStruct ::
      ResolveDeserializer ::
      ResolveNewInstance ::
      ResolveUpCast ::
      ResolveGroupingAnalytics ::
      ResolvePivot ::
      ResolveOrdinalInOrderByAndGroupBy ::
      ResolveMissingReferences ::
      ExtractGenerator ::
      ResolveGenerate ::
      ResolveFunctions :: //解析基本函數,如max,min,agg
      ResolveAliases ::
      ResolveSubquery :: //解析AST中的字查詢信息
      ResolveWindowOrder ::
      ResolveWindowFrame ::
      ResolveNaturalAndUsingJoin ::
      ExtractWindowExpressions ::
      GlobalAggregates :: //解析全局的聚合函數,比如select sum(score) from table
      ResolveAggregateFunctions ::
      TimeWindowing ::
      ResolveInlineTables ::
      TypeCoercion.typeCoercionRules ++
      extendedResolutionRules : _*),
    Batch("Nondeterministic", Once,
      PullOutNondeterministic),
    Batch("UDF", Once,
      HandleNullInputsForUDF),
    Batch("FixNullability", Once,
      FixNullability),
    Batch("Cleanup", fixedPoint,
      CleanupAliases)
  )

Optimizer模塊

Optimizer是catalyst的核心,分為RBO和CBO兩種。
RBO的優化策略就是對語法樹進行一次遍歷,模式匹配能夠滿足特定規則的節點,再進行相應的等價轉換,即將一棵樹等價地轉換為另一棵樹。SQL中經典的常見優化規則有,

  • 謂詞下推(predicate pushdown)
  • 常量累加(constant folding)
  • 列值裁剪(column pruning)
  • Limits合并(combine limits)
由下往上走,從join后再filter優化為filter再join
從`100+80`優化為`180`,避免每一條record都需要執行一次`100+80`的操作
剪裁不需要的字段,特別是嵌套里面的不需要字段。如只需people.age,不需要people.address,那么可以將address字段丟棄
//@see http://blog.csdn.net/oopsoom/article/details/38121259
//org.apache.spark.sql.catalyst.optimizer.Optimizer.scala
  def batches: Seq[Batch] = {
    // Technically some of the rules in Finish Analysis are not optimizer rules and belong more
    // in the analyzer, because they are needed for correctness (e.g. ComputeCurrentTime).
    // However, because we also use the analyzer to canonicalized queries (for view definition),
    // we do not eliminate subqueries or compute current time in the analyzer.
    Batch("Finish Analysis", Once,
      EliminateSubqueryAliases,
      ReplaceExpressions,
      ComputeCurrentTime,
      GetCurrentDatabase(sessionCatalog),
      RewriteDistinctAggregates) ::
    //////////////////////////////////////////////////////////////////////////////////////////
    // Optimizer rules start here
    //////////////////////////////////////////////////////////////////////////////////////////
    // - Do the first call of CombineUnions before starting the major Optimizer rules,
    //   since it can reduce the number of iteration and the other rules could add/move
    //   extra operators between two adjacent Union operators.
    // - Call CombineUnions again in Batch("Operator Optimizations"),
    //   since the other rules might make two separate Unions operators adjacent.
    Batch("Union", Once,
      CombineUnions) ::
    Batch("Subquery", Once,
      OptimizeSubqueries) ::
    Batch("Replace Operators", fixedPoint,
      ReplaceIntersectWithSemiJoin,
      ReplaceExceptWithAntiJoin,
      ReplaceDistinctWithAggregate) ::
    Batch("Aggregate", fixedPoint,
      RemoveLiteralFromGroupExpressions,
      RemoveRepetitionFromGroupExpressions) ::
    Batch("Operator Optimizations", fixedPoint,
      // Operator push down
      PushProjectionThroughUnion,
      ReorderJoin,
      EliminateOuterJoin,
      PushPredicateThroughJoin, //謂詞下推之一
      PushDownPredicate, //謂詞下推之一
      LimitPushDown,
      ColumnPruning, //列值剪裁,常用于聚合操作,join左右孩子操作,合并相鄰project列
      InferFiltersFromConstraints,
      // Operator combine
      CollapseRepartition,
      CollapseProject,
      CollapseWindow,
      CombineFilters, //謂詞下推之一,合并兩個相鄰的Filter。合并2個節點,就可以減少樹的深度從而減少重復執行過濾的代價
      CombineLimits, //合并Limits
      CombineUnions,
      // Constant folding and strength reduction
      NullPropagation,
      FoldablePropagation,
      OptimizeIn(conf),
      ConstantFolding, //常量累加之一
      ReorderAssociativeOperator,
      LikeSimplification,
      BooleanSimplification, //常量累加之一,布爾表達式的提前短路
      SimplifyConditionals,
      RemoveDispensableExpressions,
      SimplifyBinaryComparison,
      PruneFilters,
      EliminateSorts,
      SimplifyCasts,
      SimplifyCaseConversionExpressions,
      RewriteCorrelatedScalarSubquery,
      EliminateSerialization,
      RemoveRedundantAliases,
      RemoveRedundantProject) ::
    Batch("Check Cartesian Products", Once,
      CheckCartesianProducts(conf)) ::
    Batch("Decimal Optimizations", fixedPoint,
      DecimalAggregates) ::
    Batch("Typed Filter Optimization", fixedPoint,
      CombineTypedFilters) ::
    Batch("LocalRelation", fixedPoint,
      ConvertToLocalRelation,
      PropagateEmptyRelation) ::
    Batch("OptimizeCodegen", Once,
      OptimizeCodegen(conf)) ::
    Batch("RewriteSubquery", Once,
      RewritePredicateSubquery,
      CollapseProject) :: Nil
  }

SparkPlanner模塊

至此,OLP已經得到了比較完善的優化,然而此時OLP依然沒有辦法真正執行,它們只是邏輯上可行,實際上spark并不知道如何去執行這個OLP。

  • 比如join只是一個抽象概念,代表兩個表根據相同的id進行合并,然而具體怎么實現這個合并,邏輯執行計劃并沒有說明
optimized logical plan -> physical plan

此時就需要將左邊的OLP轉換為physical plan物理執行計劃,將邏輯上可行的執行計劃變為spark可以真正執行的計劃。

  • 比如join算子,spark根據不同場景為該算子制定了不同的算法策略,有broadcastHashJoin、shuffleHashJoin以及sortMergeJoin,物理執行計劃實際上就是在這些具體實現中挑選一個耗時最小的算法實現,這個過程涉及到cost model/CBO
CBO off
CBO on

CBO中常見的優化是join換位,以便盡量減少中間shuffle數據集大小,達到最優輸出。


Job UI

sp.prepare.PrepareController
  • WholeStageCodegen,將多個operators合并成一個java函數,從而提高執行速度
  • Project,投影/只取所需列
  • Exchange,stage間隔,產生了shuffle

Reference

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,011評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,263評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,543評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,323評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,874評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,095評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,605評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,551評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,720評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,961評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,358評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,612評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,330評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,690評論 2 370

推薦閱讀更多精彩內容