Spark 小文件合併優化實踐

隨着 Delta Lake 的開源以及 spark3 preview發佈,很多 spark/大數據 的痛點都看到了一個新的解決方向,大數據刀耕火種的時代可能就要翻篇了。這篇文章要是再不寫,怕是以後也沒機會放出來了_(:з」∠)_

背景

Spark 生成小文件是個老問題了,不再長篇大論,這裏簡單提一下形成原因及影響。

  • 原因:開發人員無法判斷作業寫出的數據量,shuffle write 階段分區數設置過多,導致寫出的文件數量多。
  • 影響:數據讀取/寫入的速度會降低,並且會對 hdfs namenode 內存造成非常大的壓力。

一般遇到這種情況,也只能讓業務或者開發人員主動的合併下數據或者控制下分區數量。早期還試過 spark 的 adaptive-execution(AE) ,不過效果也並不好,且在不少場景下還發現了一些BUG,比如單分區 hang 住之類的。

因此我們進行了一些嘗試,希望能自動化的解決/緩解此類問題。

一些工作

大致做了這麼一些工作:

  1. 修改 Spark FileFormatWriter 源碼,數據落盤時,記錄相關的metrics信息(分區/表的記錄數量+文件數量)

  2. 在發生落盤操作後,會自動觸發 merge 檢測,判斷是否需要追加合併數據任務

  3. 實現一個 MergeTable 語法用於合併表/分區碎片文件,用於系統或者用戶直接調用

第1和第2點主要是平臺化的一些工作,一旦發生數據落盤,會對metrics和作業信息經過一些校驗,判斷是否需要進入MergeTable邏輯,下面主要說一下 MergeTable 的一些細節。

MergeTable

語法:

  1. 允許指定表或者分區進行合併
  2. 如果直接合並分區表但不指定分區,則會遞歸所有分區進行合併
  3. 如果指定了生成的文件數量,就會跳過規則校驗,直接按該數量進行合併
merge table [表名] [options (fileCount=合併後文件數量)]  --非分區表
merge table [表名] PARTITION (分區信息) [options (fileCount=合併後文件數量)] --分區表

整體邏輯還是比較簡單的:
在這裏插入圖片描述

一些優化點:

  1. 只合並碎片文件

    ​ 例如設定的閾值是128M,只會讀取小於該大小的文件進行合併,如果碎片文件數量小於一定閾值,就不會觸發合併(合併任務存在一定性能開銷,允許系統中存在一定量的小文件)

  2. 分區數量及合併方式

    ​ 爲了提高性能,定義了一些規則用於計算輸出文件數量及合併方式的選擇。

    ​ 根據是否啓用 dynamicAllocation 來選擇 spark.executor.instances * spark.executor.coresspark.dynamicAllocation.maxExecutors * spark.executor.cores 來獲取當前 Spark 作業的最高併發度,該併發度將用於計算數據的分塊大小。根據數據碎片文件的總大小選擇合併(coalesce/repartition)方式。

    例子1:併發度100,碎片文件數據100,碎片文件總大小100M,如果這個時候用了 coalesce(1),很顯然只會有1個線程去讀/寫數據,如果改爲 repartition(1),則會有100個併發讀,一個線程順序寫。

    例子2:併發度100,碎片文件數量10000,碎片文件總大小100G,如果這個時候用了 repartition(200),會有100G的數據發生 shuffle,如果使用 coalesce(200),則能在相同併發的情況下避免200G數據的IO。

    例子3:併發度200,碎片文件數量10000,碎片文件總大小50G,要是使用 coalesce(100),會保存出100個500M文件,但是浪費了一半的計算性能。如果使用 coalesce(200),正常情況下合併耗時會下降爲原來的50%。

    ​ 通過上述例子,清楚 spark 運行原理的同學應該很快就能明白,這些操作的核心就是爲了儘可能多的使用計算資源以及避免不必要的IO。

  3. 修復元數據
    ​ 因爲 merge 操作會修改目錄的創建時間和訪問時間,所以在目錄替換時會額外操作將元數據信息修改到 merge 前的一個狀態,該操作還能避免冷數據掃描的誤判。

  4. commit 前進行校驗
    ​ 在最後一步會對數據做校驗,判斷合併前後數據量(從數據塊元數據中直接獲取數量,避免發生IO)是否發生變化,如果異常則會進行回滾。

後記

做了相關優化後,MergeTable 的速度對比原生暴力 Merge 的方式,在不同的數據場景下,性能會有數倍至數十倍的提升。

該方式已經在線上運行了1年多,成功的將平均文件大小從150M提升到了270M左右,同時 namenode 的內存壓力也得到了極大緩解。

很多時候,並不是一定用到許多花裏胡哨的技術才能達到目的,只要能解決問題都是好辦法。

一些不足
數據合併過程中數據不可用,其實可以通過 MVCC 就能很簡單的實現,但是會顯著提高存儲成本及運維成本。

不過合併操作通過加入我們自定義的工作流後,並不會影響到下游任務,已經滿足業務需求了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章