Hive中窗函数数据倾斜的问题

问题背景

现有源表A,包含两列,id和score

id score
1 0.3
2 0.5
8 0.9

A表大约有3亿条记录,现在需要根据score的排序将记录分为10个等级。
将score按照从小到大排序,score在前10%的记录,等级为0;10%-20%之间的,等级为1;以此类推。

id score level
1 0.3 1
2 0.5 5
8 0.9 8

如果用全局排序实现,需要对3亿条记录排序,非常慢。可以先利用分位点近似算法,近似寻找10%,20%,...,90%分位点来优化。
于是,很容易用聚合函数 percentile_approx() 通过转换为窗函数实现

select id, array_search(score, splits)
from (
    select id, score,
        percentile_approx(score, array(0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9)) over () as splits
    from A
)t

array_search 是自己写的一个UDF,寻找score所在的分段!

现象

运行的时候发现,多个reduce最后一个运行特别慢,而且当运行到100%后会卡死,直到大约20分钟结束,
在输出的数据分片中,发现所有的数据都集中到一个分片了。

解释

问题在 over () 将聚合函数转换为窗函数上,窗函数会将同一个partition的数据分到一个reduce上,
如果是 over (partition by xx),那么xx字段相同的值会在同一个reduce上,然而这里是将所有的记录当做同一个分片,
因而出现了所有数据集中到一个reduce上面的现象,进而导致写入数据的时候,只有一个reduce在写入3亿条记录,
使得看起来就像卡死在100%这个状态了!

解决办法

不要用窗函数,而用(map) join,HIVE会自动将这个join操作转为 map join。

select id, array_search(score, splits) from A
join (
    select percentile_approx(score, array(0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9)) as splits
    from A
)t

经过改造后,运行时间从之前接近1小时减少到5分钟!