本資料來自 Workday 的軟件開發工程師 Jianneng Li 在 Spark Summit North America 2020 的 《On Improving Broadcast Joins in Spark SQL》議題的分享。相關 PPT 可以到 你要的 Spark AI Summit 2020 PPT 我已經給你整理好了 裏面獲取。
背景
相信使用 Apache Spark 進行數據分析的同學對 Spark 中的 Broadcast Join 比較熟悉,其在 Join 之前會把一端比較小的表廣播到參與 Join 的 worker 端,具體如下:
如果想及時瞭解Spark、Hadoop或者HBase相關的文章,歡迎關注微信公衆號:iteblog_hadoop
相比 Shuffle Join,Broadcast Join 的優勢主要有:
•避免把大表的數據 shuffle 到其他節點;•很自然地處理數據傾斜
如果想及時瞭解Spark、Hadoop或者HBase相關的文章,歡迎關注微信公衆號:iteblog_hadoop
很多人得出結論:在 Broadcast Join 適用的情況下,Broadcast Join 是要比 Shuffle Join 快!但事實是這樣的嗎?
TPC-H 測試
在得出結論之前我們先來進行 TPC-H 測試,來看下是不是 Broadcast Join 一定要比 Shuffle Join 快。測試條件如下:
•數據集 10GB;•查詢:6千萬條數據的 lineitem 表 join 1.5千萬的 orders 表•Driver 的配置:1 core, 12 GB•Executor 的配置:一個 instance,18 cores, 102 GB
如果想及時瞭解Spark、Hadoop或者HBase相關的文章,歡迎關注微信公衆號:iteblog_hadoop
從上面可以結果可以看出,Broadcast Join 比 Shuffle Join 跑的慢!
Broadcast Join 機制
在理解上面結果之前,我們先來看下 Broadcast Join 的運行機制。
如果想及時瞭解Spark、Hadoop或者HBase相關的文章,歡迎關注微信公衆號:iteblog_hadoop
在進行 Broadcast Join 之前,Spark 需要把處於 Executor 端的數據先發送到 Driver 端,然後 Driver 端再把數據廣播到 Executor 端。如果我們需要廣播的數據比較多,比如我們把 spark.sql.autoBroadcastJoinThreshold 這個參數設置到 1G,但是我們的 Driver 端的內存值設置爲 500M,那這種情況下會導致 Driver 端出現 OOM。根據前面的分析,上面 TPC-H 結果慢是因爲:
•Driver 端需要 collects 1.5千萬條的數據;•Driver 端構建 hashtable;•Driver 把構建好的 hashtable 發送到 Executor 端;•Executor deserializes hashtable。
所以說由於當前 Broadcast Join 的運行機制,這就導致即使在 Broadcast Join 適用的情況下,Broadcast Join 不一定比 Shuffle Join 快。
過往記憶大數據提示,大家如果對這部分代碼感興趣可以參看 BroadcastExchangeExec.scala 類的相關代碼,其先調用 org.apache.spark.sql.execution.SparkPlan 類裏面的 executeCollectIterator 方法,其主要是將數據從 Executor 發送到 Driver,大家可以看到裏面調用了 getByteArrayRdd().collect():
private[spark] def executeCollectIterator(): (Long, Iterator[InternalRow]) = {
val countsAndBytes = getByteArrayRdd().collect()
val total = countsAndBytes.map(_._1).sum
val rows = countsAndBytes.iterator.flatMap(countAndBytes => decodeUnsafeRows(countAndBytes._2))
(total, rows)
}
然後到 relationFuture 變量初始化:
private1 lazy val relationFuture: Future[broadcast.Broadcast[Any]] = {
SQLExecution.withThreadLocalCaptured[broadcast.Broadcast[Any]](
sqlContext.sparkSession, BroadcastExchangeExec.executionContext) {
try {
// 這個地方就是前面說的將數據 Collect 到 Driver 端:
val (numRows, input) = child.executeCollectIterator()
// 這裏省去了一部分代碼
// Construct the relation.
val relation = mode.transform(input, Some(numRows))
// 這裏省去了一部分代碼
val beforeBroadcast = System.nanoTime()
longMetric("buildTime") += NANOSECONDS.toMillis(beforeBroadcast - beforeBuild)
// Broadcast the relation
// 這個地方就是前面說的需要先 broadcast 數據到 Executor 端
val broadcasted = sparkContext.broadcast(relation)
longMetric("broadcastTime") += NANOSECONDS.toMillis(
System.nanoTime() - beforeBroadcast)
val executionId = sparkContext.getLocalProperty(SQLExecution.EXECUTION_ID_KEY)
SQLMetrics.postDriverMetricUpdates(sparkContext, executionId, metrics.values.toSeq)
promise.trySuccess(broadcasted)
broadcasted
} catch {
// 這裏省去了一部分代碼
}
}
}
提升 Broadcast Join 的性能
針對上面的分析,我們能不能不把數據 collect 到 Driver 端,而直接在 Executor 端之間進行數據交換呢?這就是 Workday 的工程師團隊給我們帶來的 Executor 端的 broadcast,這項工作可以參見 SPARK-17556。我們來看看 Executor 端的 broadcast 工作原理:
•Executors 把 Join 需要的數據 broadcasted 給其他 Executors;•Driver 端只負責記錄 Executors 端的 block 信息,這樣其他 Executor 就可以知道 block 可以從哪些 Executor 獲取。
具體流程如下:
如果想及時瞭解Spark、Hadoop或者HBase相關的文章,歡迎關注微信公衆號:iteblog_hadoop
測試結果
Workday 的工程師分別測試了以下三種測試場景:
•數據量不變,分別測試不同 core 的性能;•lineitem 表大小不同測性能;•加大 orders 表的大小
結果如下:
如果想及時瞭解Spark、Hadoop或者HBase相關的文章,歡迎關注微信公衆號:iteblog_hadoop
如果想及時瞭解Spark、Hadoop或者HBase相關的文章,歡迎關注微信公衆號:iteblog_hadoop
如果想及時瞭解Spark、Hadoop或者HBase相關的文章,歡迎關注微信公衆號:iteblog_hadoop 總結起來就是:
•在數據量一樣的情況下,如果 core 的個數比較多,Shuffle Join 是有優勢的;•如果非廣播的表數據量數據量越來越大,Broadcast Join 是有優勢的;•如果加大廣播表的數據量,Driver 端的 Broadcast 是跑不出結果,Executor 的 Broadcast Join 是比較快的。
根據上面的結論,所以大家要知道 Broadcast 不一定比 Shuffle 快。另外,Executor 端的 Broadcast 特性是2016年9月就提的,截止到最新的 Apache Spark 3.0.0 這個功能還沒有合併到主分支,如果大家有需要這個,可以自行合併。
猜你喜歡
1、Spark 背後的商業公司收購的 Redash 是個啥?
4、Apache Spark 3.0.0 正式版終於發佈了,重要特性全面解析
過往記憶大數據微信羣,請添加微信:fangzhen0219,備註【進羣】