筆者最近在做一些任務的優化,大多的場景是因為采用row_number()
進行分組去重,所以耗時特別高。樣例代碼如下:
select *
from (
select *
from (
select *,
row_number() over(partition by id order by ts desc) rn
from table
) as a
) as a
where rn = 1
原因
要做這個操作,不得不做的就是shuffle,而且因為要保留每行數據,沒法在map端做合并,所以造成shuffle的量特別大。
優化思路
減少shuffle
map端合并
假如沒有這樣的窗口操作的算子,我們自己來實現一個這樣的算子的話,其實是可以在map端做數據合并的。
例如,我們可以把rn=1
的操作前置,map端根據分區key和ts排序之后只保留1條就可以,然后reduce端把多個分區的1條記錄合并再得到最終的一條。
目前Spark并沒有這樣的算子,但是可以用其他算子模仿一下,比如max算子,示例代碼如下:
select id, split(tuple, ',') arr
from (
select id, max(concat_ws(',', ts, col1, col2, ..)) tuple
from table
group by id
) as a
在筆者的場景中,確實是可以減少shuffle的數據量,但是下一個stage會很耗時。所以這個是一個策略,但是需要按照自己的情況測試下,也要結合數據量。
如果重復的數據不多,這種策略并不是很好。
復用分區
這個是筆者偶然間發現的一個場景。
筆者的業務SQL如下:
select a.*, b.col
from (
select *, row_number() over (partition by id, fid order by client_time desc) rn
from table
) as a
left join mapping_tb as b
on a.id = b.id
where rn = 1
業務邏輯:
分區去重+關聯維度表
執行計劃如下:
+- SortMergeJoin LeftOuter (18)
:- Sort (10)
: +- Exchange (9)
: +- Filter (7)
: +- Window (6)
: +- Sort (5)
: +- ShuffleQueryStage (4)
: +- Exchange (3)
: +- * ColumnarToRow (2)
: +- Scan orc table (1)
+- Sort (17)
+- ShuffleQueryStage (16)
+- Exchange (15)
+- * Project (14)
+- * Filter (13)
+- * ColumnarToRow (12)
+- Scan orc mapping_tb (11)
可以看到關聯維度表用的key為id,分區去重用的key是id+fid,前者的分區肯定包含后者,是不是可以在這上面做些文章。
帶著這樣的懷疑,對邏輯進行了調整,如下:
select *
from (
select *, row_number() over (partition by id, fid order by client_time desc) rn
from (
select a.*, b.col
from table as a
left join mapping_tb as b
on a.id = b.id
) as a
) as a
where rn = 1
執行計劃如下:
+- Filter (17)
+- Window (16)
+- Sort (15)
+- Project (14)
+- SortMergeJoin LeftOuter (13)
:- Sort (5)
: +- ShuffleQueryStage (4)
: +- Exchange (3)
: +- * ColumnarToRow (2)
: +- Scan orc table (1)
+- Sort (12)
+- ShuffleQueryStage (11)
+- Exchange (10)
+- * Project (9)
+- * Filter (8)
+- * ColumnarToRow (7)
+- Scan orc mapping_tb (6)
從這里可以看出SortMergeJoin
之后做窗口操作,并沒有做Shuffle,而是直接接了一個Sort
操作,這就是復用分區。
出于對比我們將上述的SQL做調整并比較執行計劃。
select *
from (
select *, row_number() over (partition by id2, fid order by client_time desc) rn
from (
select a.*, b.col
from table as a
left join mapping_tb as b
on a.id = b.id
) as a
) as a
where rn = 1
執行計劃如下:
+- Filter (18)
+- Window (17)
+- Sort (16)
+- Exchange (15)
+- Project (14)
+- SortMergeJoin LeftOuter (13)
:- Sort (5)
: +- ShuffleQueryStage (4)
: +- Exchange (3)
: +- * ColumnarToRow (2)
: +- Scan orc table (1)
+- Sort (12)
+- ShuffleQueryStage (11)
+- Exchange (10)
+- * Project (9)
+- * Filter (8)
+- * ColumnarToRow (7)
+- Scan orc mapping_tb (6)
可以看到,修改之后的SQL執行計劃多了一個Exchange (15)
,說明這里需要再做一次Shuffle。
實際在執行過程中,Shuffle采用了id
做分區,Shuffle的數據量能減少大概1倍,而且后續沒有再次的shuffle,即我們將shuffle的數據量減少了1倍。