1. 程式人生 > >一次 Spark SQL 效能提升10倍的經歷

一次 Spark SQL 效能提升10倍的經歷

是醬紫的,簡單來說:併發執行 spark job 的時候,併發的提速很不明顯。

what_are_you_talking_about.png

嗯,且聽我慢慢道來,囉嗦點說,類似於我們內部有一個系統給分析師用,他們寫一些 sql,在我們的 spark cluster 上跑。隨著分析師越來越多,sql job 也越來越多,等待執行的時間也越來越長,我們就在想怎麼把 sql 執行的時間加快一點。我們的整個架構是 spark 1.6.1 on YARN 的,經過分析一些 sql 發現其實大多數分析語句都是比較簡單的統計 sql,叢集資源也還算多,一條簡單的 sql 語句就把整個叢集資源的坑佔著略顯不合適,有點飛機馬達裝到拖拉機上的趕腳,所以第一步,我們想,支援 spark job 的並行執行。

ok,初步方案有了,我們就做了如下幾步改善工作:

  • 首先設定 spark.scheduler.mode 為 FAIR 模式,首先 spark.scheduler.mode 有 FIFOFAIR 兩種模式,FIFO 是說提交的job,都是順序執行的,後提交的 job 一定要等之前提交的 job 完全執行結束後才可以執行;FAIR 是說,如果之前提交的 job 沒有用完叢集資源的話,後提交的job可以即刻開始執行。關於這點在官方文件上有詳細的解釋:

    schedule_mode.png

  • 其次,我們生成了 10 個 pool,所謂的 pool,可以理解為資源池,或者通道。你可以在提交 job 的時候指定提交到哪個 pool 裡面,可以簡單的理解為我們把所有的叢集資源分成 10 份,然後在提交 job 的時候指定在哪一份資源中執行這個 job。

    10_pool.png

  • 最後,我們在提交 job 的時候指定提交到的 pool 名字,只需要在提交 job 之前設定一個 sparkContext 的引數即可: sc.setLocalProperty("spark.scheduler.pool", "your_pool_id")

    pool_id.png

看似很簡單,但能知道上面這些配置的也算是用 spark 比較熟練的人了吧,我迫不及待的測試了一下速度,發現了一個從古至今的大真理:理想很美好,現實很骨幹啊。測試下來,發現多個 job 並行執行的時間並沒有節省多少。

上面把問題說得很清楚了:多 job 並行的時候,執行速度並沒有明顯提升。但是原理上應該不會如此,只要一個 sql job 不需要全域性所有叢集資源,理論上來說會有較大提升的。下面是一組簡單的資料對比:

data_contrast_1.png

雖然看到,平行計算後時間只需要之前的 50%,但是這裡需要說明一下,這個資料不夠穩定的哦,比如說偶爾會新增 10來秒 這樣子的。這裡 暫且接受提升 50% 的速度這樣一個結論吧

但是,理論上來說,還能提升更多,不滿足 50% 的提升效率,我們接著深度解讀 spark web ui 上的一些分析資料,嘗試找找能否把速度再度提升一下。終於找到了核心原因,下面我就把整個排查的過程詳細記錄下來:

  • 找一個花費時間較長的 job,進去看看執行的詳情,這裡我們用 job id 為 796 的這個 job

    spark_optimize_1.png

  • 發現 job 796 有兩個 stage,且有 99% 的時間都花在第一個 stage 1590 上了,而且需要注意的是,這個 stage 有 237.6mb 的資料讀取,有可能需要經過網路從其他 hdfs 節點讀過來,難道跟網路 I/O 有關?繼續點進去看看。

    spark_optimize_2.png

  • 進來這個 stage 內部,似乎發現問題所在了,首先我們先關注下圖中標記的幾個點,可以總結出幾個點:

    • 首先,該 stage 內的所有任務在 executor 上真正執行的時間【可以理解為 cpu time】是 2s
    • 其次,該 stage 內任務執行完成的時間是 1.1 m,大概是 66s,可以理解為【wall time】
    • 該 stage 內所有的 task,schedule delay 的時間中位數是 0.5s,最大達到 1s【真正執行的時間也才 2s 哦】
    • 該 stage 內一共有 336 個task

    spark_optimize_3.pngspark_optimize_4.png

到這裡,問題根源基本上已經知道了,即 job 796 的大多數時間都被消耗在 stage 1590 的 336 個task 的 secheduler delay 上面了。

上面問題幾乎已經明確了,現在就該看看腫麼解決了。我當時是這樣去考慮的:

  • 為什麼 scheduler delay 會這麼大

因為資源不夠,要解決這個問題,似乎唯一的辦法就是增加叢集資源了。可是哥們,叢集是你想加就能加的嗎?那可是要砸錢的呀?而且如果公司缺機器的話,想加叢集資源也要經過 申請->審批->採購->分配->叢集配置 大大小小几個階段,說不一定等你找到女朋友了都還沒搞定啊。

當時想著加資源這個方案短期不可取後,有那麼幾分鐘是覺得有點燒腦的。我就靜靜的看著 web ui,心裡在算,一個 task 如果平均 scheduler delay 0.5s 的話,這 336 個 task 就得 delay 118 秒,基本上都到 2 分鐘了。這 delay 的時間可真夠長的啊,就在算這個數值的時候,突然想到這樣一個公式:total delay time = average delay time * task number。現在我們的問題是要解決 total delay time,那完全可以從兩方面去解決呀:

  • 降低 average delay time:目前來看似乎唯一的方法是砸錢加資源
  • 降低 task 數:粗略來看,簡單的降低 task 數的話,應該是能減少 total delay time 的,但是如果task 數降低了,意味著每個 task 需要處理的資料量就多了,那其他的時間應該是會增加一些的,比如說 Task Deserialization Time, Result Serialization Time, GC Time, Duration 等。減少 task 數究竟能不能提高整體執行速度,似乎乍一看還真不好確定。

反正砸錢加資源這個方案暫時是行不通的,要不就再仔細分析一下降低task數這個方案。這裡我們在仔細參考一下下圖中這一列指標:

spark_optimize_5.pngspark_optimize_6.png

我們用 75 分位的統計資料來做一個假設:假設我們把每一個 task 的資料量加 10 倍,那麼預計的 task metrics 75 分位大概是一個什麼樣的數值,假設這些指標都是線性增長的話:

  • Duration: 擴大到 10 倍,14ms
  • Scheduler Delay: 這個指標不用估計
  • Task Deserialization Time: 擴大到 10 倍,6ms
  • GC Time: 擴大到 10 倍,最多1ms
  • Result Serialization Time: 擴大到 10 倍,最多1ms
  • Getting Result Time: 擴大到 10 倍,最多1ms
  • Peak Execution Memory: 擴大到 10 倍,最多 1b
  • Input Size / Records: 擴大到 10 倍,918.8 KB * 2 / 2
  • Shuffle Write Size / Records 0: 擴大到 10 倍,294.0 B * 2/ 20

可以看到,這樣大概估計下來,除去 Scheduler Delay 的時間,其實其他時間也沒消耗多少,都是毫秒級的,看起來應該是完全可行的呀。

正準備這樣測試的時候,我忽然想到,為什麼現在的 metrics 統計是這樣的結構的啊,這麼多 task?一般來說,一個 task 對應到 hdfs 上的一個 parquet 檔案【該專案中所有資料檔案都是用 parquet 壓縮後儲存到 hdfs 上的】,難道是現在存在 hdfs 上的 parquet 檔案個數過多,每個檔案太小?突然有一種恍然大悟的感覺,趕緊看看現在 hdfs 上檔案的結構,如下所示:

 
  1. [email protected]:~/Desktop/dyes/git-mercury/mercury-computing$hadoop fs -ls -h hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161117/20161117_Transaction_SZ | cat -n | tail
  2. 317 -rw-r--r-- 3 taotao hfmkt 1.3 M 2016-11-17 16:37 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161117/20161117_Transaction_SZ/part-r-00310-38d7cc53-60d2-40b3-a945-0cb5832f30de.gz.parquet
  3. 318 -rw-r--r-- 3 taotao hfmkt 1.4 M 2016-11-17 16:37 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161117/20161117_Transaction_SZ/part-r-00311-38d7cc53-60d2-40b3-a945-0cb5832f30de.gz.parquet
  4. 319 -rw-r--r-- 3 taotao hfmkt 2.9 M 2016-11-17 16:37 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161117/20161117_Transaction_SZ/part-r-00312-38d7cc53-60d2-40b3-a945-0cb5832f30de.gz.parquet
  5. 320 -rw-r--r-- 3 taotao hfmkt 1.2 M 2016-11-17 16:37 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161117/20161117_Transaction_SZ/part-r-00313-38d7cc53-60d2-40b3-a945-0cb5832f30de.gz.parquet
  6. 321 -rw-r--r-- 3 taotao hfmkt 1.9 M 2016-11-17 16:37 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161117/20161117_Transaction_SZ/part-r-00314-38d7cc53-60d2-40b3-a945-0cb5832f30de.gz.parquet
  7. 322 -rw-r--r-- 3 taotao hfmkt 1.7 M 2016-11-17 16:37 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161117/20161117_Transaction_SZ/part-r-00315-38d7cc53-60d2-40b3-a945-0cb5832f30de.gz.parquet
  8. 323 -rw-r--r-- 3 taotao hfmkt 899.4 K 2016-11-17 16:37 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161117/20161117_Transaction_SZ/part-r-00316-38d7cc53-60d2-40b3-a945-0cb5832f30de.gz.parquet
  9. 324 -rw-r--r-- 3 taotao hfmkt 2.3 M 2016-11-17 16:37 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161117/20161117_Transaction_SZ/part-r-00317-38d7cc53-60d2-40b3-a945-0cb5832f30de.gz.parquet
  10. 325 -rw-r--r-- 3 taotao hfmkt 1.0 M 2016-11-17 16:37 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161117/20161117_Transaction_SZ/part-r-00318-38d7cc53-60d2-40b3-a945-0cb5832f30de.gz.parquet
  11. 326 -rw-r--r-- 3 taotao hfmkt 460.9 K 2016-11-17 16:37 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161117/20161117_Transaction_SZ/part-r-00319-38d7cc53-60d2-40b3-a945-0cb5832f30de.gz.parquet

可以看到,現在有 300 多個檔案【上面只是一部分,還有十幾個在另外一個資料夾裡,一個 sql 會統計兩個資料夾裡的資料檔案】,而且我仔細看了一下,每個檔案大小最小的有很多 1kb 的,最大的有 2.9mb 的。難怪了,原來核心根源在這裡。再結合上面關於 metrics 的分析,我心裡大概確信了,只要把 parquet 檔案的問題解決就行了,方法就是壓縮 parquet 檔案個數,控制每個 parquet 檔案的大小即可。

bingo.png

方法確定了,那就幹咯。

未來方便對比,我把 20161212 的資料檔案處理了一下,保留 20161117 這天的資料檔案【20161212 的資料檔案整體上比 20161117 的資料檔案要多 10%】,下面是對比結果:

parquet 檔案個數

  • 20161117 這天
 
  1. [email protected]:~/Desktop/dyes/git-mercury/mercury-computing$hadoop fs -ls -h hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161117/20161117_Transaction_S* | cat -n | tail -n 5
  2. 342 -rw-r--r-- 3 taotao hfmkt 1.7 M 2016-11-17 16:37 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161117/20161117_Transaction_SZ/part-r-00315-38d7cc53-60d2-40b3-a945-0cb5832f30de.gz.parquet
  3. 343 -rw-r--r-- 3 taotao hfmkt 899.4 K 2016-11-17 16:37 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161117/20161117_Transaction_SZ/part-r-00316-38d7cc53-60d2-40b3-a945-0cb5832f30de.gz.parquet
  4. 344 -rw-r--r-- 3 taotao hfmkt 2.3 M 2016-11-17 16:37 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161117/20161117_Transaction_SZ/part-r-00317-38d7cc53-60d2-40b3-a945-0cb5832f30de.gz.parquet
  5. 345 -rw-r--r-- 3 taotao hfmkt 1.0 M 2016-11-17 16:37 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161117/20161117_Transaction_SZ/part-r-00318-38d7cc53-60d2-40b3-a945-0cb5832f30de.gz.parquet
  6. 346 -rw-r--r-- 3 taotao hfmkt 460.9 K 2016-11-17 16:37 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161117/20161117_Transaction_SZ/part-r-00319-38d7cc53-60d2-40b3-a945-0cb5832f30de.gz.parquet
  • 20161212 這天
 
  1. [email protected]:~/Desktop/dyes/git-mercury/mercury-computing$hadoop fs -ls -h hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161212/20161212_Transaction_S* | cat -n | tail -n 5
  2. 34 -rw-r--r-- 3 taotao hfmkt 19.2 M 2016-12-12 15:49 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161212/20161212_Transaction_SZ/part-r-00013-686bbce5-a7a1-4b5d-b25c-14cd9ddae283.gz.parquet
  3. 35 -rw-r--r-- 3 taotao hfmkt 10.7 M 2016-12-12 15:49 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161212/20161212_Transaction_SZ/part-r-00014-686bbce5-a7a1-4b5d-b25c-14cd9ddae283.gz.parquet
  4. 36 -rw-r--r-- 3 taotao hfmkt 26.0 M 2016-12-12 15:49 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161212/20161212_Transaction_SZ/part-r-00015-686bbce5-a7a1-4b5d-b25c-14cd9ddae283.gz.parquet
  5. 37 -rw-r--r-- 3 taotao hfmkt 20.1 M 2016-12-12 15:49 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161212/20161212_Transaction_SZ/part-r-00016-686bbce5-a7a1-4b5d-b25c-14cd9ddae283.gz.parquet
  6. 38 -rw-r--r-- 3 taotao hfmkt 8.7 M 2016-12-12 15:49 hdfs://hadoop-archive-cluster/hfmkt/level2/datayes/parquet/20161212/20161212_Transaction_SZ/part-r-00017-686bbce5-a7a1-4b5d-b25c-14cd9ddae283.gz.parquet

100個job併發執行時間

  • 20161117 這天:99s
  • 20161212 這天:16s

Spark Web UI 上一個 job 對比

  • 20161117 這天

spark_optimize_2.pngspark_optimize_3.pngspark_optimize_4.png

  • 20161212 這天

spark_optimize_7.pngspark_optimize_9.pngspark_optimize_8.png

首先,需要說明的是,這次優化應該還有提升的空間,雖然優化後整體從 204s 到 99s 再到 16s,提升了十倍多,確實很大,但是最後我們還是發現 16s 的情況下,scheduler delay 和 Task Deserialization Time 還是有佔用了大部分時間,這裡我覺得不能一味的在檔案個數和大小上下功夫了。需要考慮到使用者場景來做一個權衡。所以越到後期的優化,越考驗產品功能的設計,當然這是後話了,就不在本文範圍內討論。

其次,這次優化,從發現問題,追根溯源,到最後解決問題,大概花了 1 小時,基本上還算不錯。通過這次排查,還是真心感受到 spark 設計的完善,不得不說,作為一個開源專案,spark 最大的特點,我覺得應該是 spark 是由一幫非程式設計師設計實現的,而是一幫由程式設計師,架構師,產品經理組合起來一起幹的,更像是一個產品,而不是一個開源專案。怪不得這幫人要去開個公司【databricks:我最看好的公司之一】,看來真的是 born this way。

原文連結:

https://litaotao.github.io/index.html

https://litaotao.github.io/introduction-to-spark?s=inner