Spark Aggregations execution

一、Distinct aggregation 算法

包含 distinct 關鍵字的 aggregation 由 4 個物理執行步驟組成。我們使用以下 query 來介紹:

val dataset = Seq(
(1, "a"), (1, "a"), (1, "a"), (2, "b"), (2, "b"), (3, "c"), (3, "c")
).toDF("nr", "letter")
 
dataset.groupBy($"nr").agg(functions.countDistinct("letter")).explain(true)

① partial aggregation 步驟

第一步是創建一個 partial aggregate,此 partial aggregate 的 grouping key 將不僅包括 query 中定義的 grouping key(nr),還包含 distinct 的列(letter),效果如 group by nr、letter,執行計劃如下:

HashAggregate(keys=[nr#5, letter#6], functions=[], output=[nr#5, letter#6])
  +- LocalTableScan [nr#5, letter#6]

② partial merge aggregation 步驟

這一步將通過 shuffle 將具有相同 grouping key(此處為 nr、letter)的數據劃分為同一分區:

+- HashAggregate(keys=[nr#5, letter#6], functions=[], output=[nr#5, letter#6])  
   +- Exchange hashpartitioning(nr#5, letter#6, 200)
        +- HashAggregate(keys=[nr#5, letter#6], functions=[], output=[nr#5, letter#6])
            +- LocalTableScan [nr#5, letter#6]

③ partial aggregation for distinct 步驟

第三步,Spark 最終開始執行聚合,執行的是 partial aggregate:

+- HashAggregate(keys=[nr#5], functions=[partial_count(distinct letter#6)], output=[nr#5, count#18L])
    +- HashAggregate(keys=[nr#5, letter#6], functions=[], output=[nr#5, letter#6])
        +- Exchange hashpartitioning(nr#5, letter#6, 200)
            +- HashAggregate(keys=[nr#5, letter#6], functions=[], output=[nr#5, letter#6])
                +- LocalTableScan [nr#5, letter#6]

④ final aggregation 步驟

第四步,partial aggregate(第三步)的結果將合并到最終結果中,并進行返回。它涉及 shuffle:

HashAggregate(keys=[nr#5], functions=[count(distinct letter#6)], output=[nr#5, count(DISTINCT letter)#12L])
+- Exchange hashpartitioning(nr#5, 200)
      +- HashAggregate(keys=[nr#5], functions=[partial_count(distinct letter#6)], output=[nr#5, count#18L])
             +- HashAggregate(keys=[nr#5, letter#6], functions=[], output=[nr#5, letter#6])
                +- Exchange hashpartitioning(nr#5, letter#6, 200)
                   +- HashAggregate(keys=[nr#5, letter#6], functions=[], output=[nr#5, letter#6])
                      +- LocalTableScan [nr#5, letter#6]

我們用下面的這張圖來總結上述幾個步驟:


二、無 Distinct aggregation 算法

無 Distinct aggregation 會簡單一些,僅包含兩個步驟,我們通過下面的例子來說明:

val dataset = Seq(
  (1, "a"), (1, "a"), (1, "a"), (2, "b"), (2, "b"), (3, "c"), (3, "c")
).toDF("nr", "letter")
dataset.groupBy($"nr").count().explain(true)

①、partial aggregations 步驟

第一步即進行局部聚合:

HashAggregate(keys=[nr#5], functions=[partial_count(1)], output=[nr#5, count#17L])
+- PlanLater LocalRelation [nr#5]

②、final aggregation 步驟

第二步,毫無疑問,對部分結果進行了最終匯總:

HashAggregate(keys=[nr#5], functions=[count(1)], output=[nr#5, count#12L])
+- HashAggregate(keys=[nr#5], functions=[partial_count(1)], output=[nr#5, count#17L])
   +- PlanLater LocalRelation [nr#5]

三、Hash-based 和 Sort-based aggregation

上述兩種模式都會調用到 createAggregate 方法,該方法為以下 3 種策略創建物理執行計劃:

  • hash-based
  • object-hash-based
  • sort-based

這 3 中策略有一些共性。一個 Spark Sql aggregation 主要由兩部分組成:

  • 一個 agg buffer(聚合緩沖區:包含 grouping keys 和 agg value)
  • 一個 agg state(聚合狀態:僅 agg value)

每次調用 GROUP BY key 并對其使用一些聚合時,框架都會創建一個聚合緩沖區,保留給定的聚合(GROUP BY key)。指定 key(COUNT,SUM等)所涉及的聚合都在此聚合緩沖區存儲其部分(partial)或最終聚合結果,稱為聚合狀態。該狀態的存儲格式取決于聚合:

  • 對于 AVG,它將是2個值,一個是出現次數,另一個是值的總和
  • 對于 MIN,它將是到目前為止所看到的最小值
    依此類推

hash-based 策略使用可變的、原始的、固定 size 的類型來作為 agg state,包括:

  • NullType
  • BooleanType
  • ByteType
  • ShortType
  • IntegerType
  • LongType
  • FloatType
  • DoubleType
  • DateType
  • TimestampType
    這里的可變能力非常重要,因為 Spark 會直接修改該值(如對于 count 來說,遇到新的 row,就會把 count 的值(agg state)加上 1)。

對于 agg state 的值是其他類型的情況,使用 object-hash-based 策略,該策略自 2.2.0 版本引入,目的是為了解決 hash-based 策略的局限性(必須使用可變的、原始的、固定 size 的類型來作為 agg state)。在 2.2.0 之前,針對 HashAggregateExec 不支持的其他類型執行的聚合都會轉換為 sort-based 的策略。大部分情況下,sort-based 的性能會比 hash-based 的差,因為在聚合前會進行額外的排序。通過參數 spark.sql.execution.useObjectHashAggregateExec 來控制是否使用 object-hash-based 聚合,默認為 true。我們通過下面的例子來理解 sort-basedobject-hash-based 的區別:
查詢

val dataset2 = Seq(
      (1, "a"), (1, "aa"), (1, "a"), (2, "b"), (2, "b"), (3, "c"), (3, "c")
    ).toDF("nr", "letter")
    dataset2.groupBy("nr").agg(functions.collect_list("letter").as("collected_letters")).explain(true)

如你所見,上圖兩個物理執行計劃均只進行一次 shuffle,但 sort-based 聚合相對于 object-hash-based 額外多了兩次排序,帶來性能開銷。

另一個值得關注的點是,hash-basedobject-hash-based 運行過程中如果內存不夠用,會切換成 sort-based 聚合。對于 object-hash-based 聚合,通過參數 spark.sql.objectHashAggregate.sortBased.fallbackThreshold 控內存中(一種 hashMap)最多持有多少個 agg buffer(一個 grouping key 的組合一個),若超過該值,則切換為 sort-based agg,該配置默認值為 128。如果切換為 sort-based agg,會打印如下日志:

ObjectAggregationIterator: Aggregation hash map reaches threshold capacity (128 entries), spilling and falling back to sort based aggregation. You may change the threshold by adjust option spark.sql.objectHashAggregate.sortBased.fallbackThreshold

對于 hash-based,該值為 Integer.MaxValue

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

推薦閱讀更多精彩內容