背景
在剛使用hive的過程中,碰到過很多問題,任務經常需要運行7,8個小時甚至更久,在此記錄一下這個過程中,我的一些收獲
join長尾
背景
SQL在Join執行階段會將Join Key相同的數據分發到同一個執行Instance上處理。如果某個Key上的數據量比較多,會導致該Instance執行時間比其他Instance執行時間長。其表現為:執行日志中該Join Task的大部分Instance都已執行完成,但少數幾個Instance一直處於執行中,這種現象稱之為長尾
長尾類別&優化方法
小表長尾
Join傾斜時,如果某路輸入比較小,可以采用Mapjoin避免傾斜。Mapjoin的原理是將Join操作提前到Map端執行,這樣可以避免因為分發Key不均勻導致數據傾斜。但是Mapjoin的使用有限制,必須是Join中的從表比較小才可用。所謂從表,即Left Outer Join中的右表,或者Right Outer Join中的左表。
熱點值長尾
如果是因為熱點值導致長尾,並且Join的輸入比較大無法用Mapjoin,可以先將熱點Key取出,對於主表數據用熱點Key切分成熱點數據和非熱點數據兩部分分別處理,最后合並。我們舉一個電商的例子,假設我們需要計算所有商品的pv。我們有如下兩張表
日志表 log 用戶點擊的日志, 包含商品的id p_id
商品表 product 包含商品名稱 p_name, 商品id p_id
- 取熱點值, 取商品pv大於10000的商品到臨時表
INSERT TABLE topk_product
SELECT
distinct p_id
FROM
(
SELECT
p_id,
count(1) AS pv
FROM log
GROUP BY p_id
) a
WHERE pv >= 10000
- 取出非熱點值和商品(product) join 得到非熱點商品的pv
SELECT p.p_id
, p.p_name
, l.pv
FROM (
SELECT p_id
, p_name
FROM product
) p
JOIN (
SELECT /*+mapjoin(b)*/ a.*
FROM (
SELECT p_id
, COUNT(1) AS pv
FROM log
) a
LEFT OUTER JOIN (
SELECT p_id
FROM topk_product
) b
ON a.p_id = b.p_id
AND b.p_id IS NULL
) l
ON p.p_id = l.p_id
- 取出熱點值和商品(product) join 得到熱點商品的pv
SELECT p.p_id
, p.p_name
, l.pv
FROM (
SELECT /*+mapjoin(b)*/ a.*
FROM (
SELECT p_id
, p_name
FROM product
) a
JOIN (
SELECT p_id
FROM topk_product
) b
ON a.p_id = b.p_id
) p
JOIN (
SELECT /*+mapjoin(d)*/ c.*
FROM (
SELECT p_id
, COUNT(1) AS pv
FROM log
) c
JOIN (
SELECT p_id
FROM topk_product
) d
ON c.p_id = d.p_id
) l
ON p.p_id = l.p_id
- union all 熱點和非熱點的數據
空值長尾
join時,假設左表(left_table)存在大量的空值,空值聚集到一個reduce上。由於left_table 存在大量的記錄,無法使用mapjoin 。此時可以使用 coalesce(left_table.key, rand()*9999)將key為空的情況下賦予隨機值,來避免空值集中造成長尾。
map長尾
Map端讀取數據時,由於文件大小分布不均勻,一些map任務讀取並處理的數據特別多,一些map任務處理的數據特別少,造成map端長尾。這種情形沒有特別好的方法,只能調節splitsize來增加mapper數量,讓數據分片更小,以期望獲得更均勻的分配。
reduce長尾
由於Distinct操作的存在,數據無法在Map端的Shuffle階段根據Group By先做一次聚合操作,減少傳輸的數據量,而是將所有的數據都傳輸到Reduce端,當Key的數據分發不均勻時,就會導致Reduce端長尾,特別當多個Distinct同時出現在一段SQL代碼中時,數據會被分發多次,不僅會造成數據膨脹N倍,也會把長尾現象放大N倍。
我們用代碼舉個例子:
只有一個distinct 的情況
- 原sql
SELECT D1
, D2
, COUNT(DISTINCT CASE
WHEN A IS NOT NULL THEN B
END) AS B_distinct_cnt
FROM xxx
GROUP BY D1,
D2
- 改后的sql
create table tmp1
as
select D1,D2,B,
count( case when A is not null then B end ) as B_cnt
from xxx
group by D1, D1, B
select D1,D2,
sum(case when B_cnt > 0 then 1 else 0 end) as B_distinct_cnt
from tmp1
group by D1,D2
多個distinct的情況
- 原始sql
select D1,D2,
count(distinct case when A is not null then B end) as B_distinct_cnt ,
count(distinct case when E is not null then C end) as C_distinct_cnt
from xxx group by D1,D2
- 修改后的sql
create table tmp1
as
select D1,D2,B,
count( case when A is not null then B end ) as B_cnt
from xxx
group by D1, D1, B
create table tmp1_1
as
select D1,D2,
sum(case when B_cnt > 0 then 1 else 0 end) as B_distinct_cnt
from tmp1
group by D1,D2
create table tmp2
as
select D1,D2,C,
count( case when E is not null then C end ) as C_cnt
from xxx
group by D1, D1, C
create table tmp2_1
as
select D1,D2,
sum(case when C_cnt > 0 then 1 else 0 end) as C_distinct_cnt
from tmp1
group by D1,D2
select
t1.D1,t1.D2,
t1.B_distinct_cnt,
t2.C_distinct_cnt
from tmp1_1 t1
left outer join tmp2_1 t2
on t1.D1=t2.D1 and t1.D2=t2.D2
