背景
公司最近在利用hive構(gòu)建數(shù)倉,聽同事們說在構(gòu)建一個(gè)超寬的維度表時(shí)運(yùn)行時(shí)長超6000s,這個(gè)時(shí)長肯定是不能接受的,不過倒是引起了我的興趣,讓同事把sql發(fā)過來看看。
現(xiàn)象
拿到sql初步分析,并沒有發(fā)現(xiàn)很明顯的大問題,就是10個(gè)千萬級(3千萬到6千萬)的表做left join,部分表有一些group by,以及rank去重的一些操作。起初以為又是涉及的源表統(tǒng)計(jì)信息未更新導(dǎo)致執(zhí)行計(jì)劃走錯(cuò)了。首先就將sql涉及的表提取出來,進(jìn)行統(tǒng)計(jì)信息更新,然后執(zhí)行sql,看見進(jìn)度一點(diǎn)點(diǎn)漲上去,還以為真就是統(tǒng)計(jì)信息的問題。600s左右進(jìn)度已經(jīng)達(dá)到99%,還剩下最后一個(gè)reducer。當(dāng)時(shí)也還有其他事情,也就讓他一直跑著,順便看看要跑多久才能完,快到6000s的時(shí)候,還是沒有完的跡象,放棄了,殺掉它吧。
分析
從上面的現(xiàn)象看來,很明顯發(fā)生了數(shù)據(jù)傾斜,而且應(yīng)該是很嚴(yán)重的傾斜。但是數(shù)據(jù)傾斜往往是發(fā)生在join key重復(fù)比較嚴(yán)重的時(shí)候,但是這條sql里面參與join的基本都是可以做主鍵的列,重復(fù)度很低。
我的第一個(gè)反應(yīng)是,join的hash算法剛好針對這些key產(chǎn)生了很差的數(shù)據(jù)分割,導(dǎo)致絕大部分?jǐn)?shù)據(jù)最后分到了一個(gè)reducer中去。首先嘗試了將key計(jì)算md5后再做join,幾乎沒有效果。但還是不死心,也想搞清楚,hive執(zhí)行join時(shí),是根據(jù)什么算法將key分散到不同reducer。可是Google、百度都沒有找到很確切的說法,如果真要搞清楚,估計(jì)只能去讀源碼了。但也說明,其他人在解決數(shù)據(jù)傾斜問題的時(shí)候,這個(gè)點(diǎn)應(yīng)該是不會(huì)構(gòu)成問題。
再繼續(xù)分析sql吧,先看看這條sql的Tez DAG。

既然是在卡最后一個(gè)reducer,那么就把圖中框起來的部分表單獨(dú)拿出來跑,果不其然,這幾個(gè)表單獨(dú)跑也會(huì)發(fā)生數(shù)據(jù)傾斜的情況。進(jìn)一步剔除一部分表,同時(shí)為了盡量縮短時(shí)間,只將這幾個(gè)表需要關(guān)聯(lián)的列拿出來操作,最后定位到如果與crcd和db進(jìn)行join時(shí)就會(huì)導(dǎo)致不可接受的傾斜時(shí)長。這幾個(gè)表中,rco,cm,dm,cd都是通過dm的id進(jìn)行關(guān)聯(lián),crcd再通過crcd的id與cd的屬性列關(guān)聯(lián),db再通過自己的id與crcd的屬性列關(guān)聯(lián)。只需要取出cd,crcd,db依次進(jìn)行l(wèi)eft join就會(huì)導(dǎo)致嚴(yán)重?cái)?shù)據(jù)傾斜。當(dāng)時(shí)又想去找出hive究竟按什么算法對key進(jìn)行分割,不過又一想還不如看看傾斜后的數(shù)據(jù)究竟是什么樣子。
怎么看呢?第一步執(zhí)行下面類似的語句針對這三個(gè)表的join創(chuàng)建一個(gè)臨時(shí)表(hive的默認(rèn)情況下,每個(gè)reducer都會(huì)產(chǎn)生一個(gè)hdfs文件)。
create table tmp_cd_db as
select cd.crcd_id,db.id from cd left join crcd on cd.crcd_id = crcd.id left join db on crcd.db_id = db.id;
第二步,直接到hdfs上這個(gè)臨時(shí)表的目錄下,把最大的那個(gè)文件拷下來hadoop fs -copyToLocal ....
第三步,vi打開文件。
打開文件的一瞬間就明白為什么會(huì)傾斜了。
根本原因
打開文件的第一瞬間,除了crcd_id有值,db.id的值全部為NULL。再用hive sql驗(yàn)證一下,統(tǒng)計(jì)出db.id為NULL的行數(shù)約3000萬,而非NULL的行數(shù)只有20多萬。原來是大量重復(fù)的NULL導(dǎo)致了傾斜。20多萬行用1000個(gè)reducer處理,3000萬行一個(gè)reducer處理,當(dāng)關(guān)聯(lián)上220列數(shù)據(jù)時(shí),這最后一個(gè)reducer的處理時(shí)長又會(huì)放大百倍左右。(注:這里并不是crcd與db關(guān)聯(lián)不上產(chǎn)生的NULL,而是cd與crcd關(guān)聯(lián)時(shí)關(guān)聯(lián)不上,產(chǎn)生了大量的為空值的crcd.db_id,而再根據(jù)這些空值與db關(guān)聯(lián)而導(dǎo)致的傾斜。)
另外一個(gè)方面的原因,還是數(shù)據(jù)質(zhì)量存在問題,且建模前,也未對數(shù)據(jù)質(zhì)量進(jìn)行判斷,就整合了這部分?jǐn)?shù)據(jù)整合到模型。
解決辦法
解決辦法就很簡單了,如果這能關(guān)聯(lián)上的20萬數(shù)據(jù)可以暫時(shí)不要的話,那么只需要簡單去掉這幾個(gè)表(關(guān)聯(lián)crcd,ccd,dmb都是為了取dmb的屬性)。只是簡單去掉這幾張表,其他不做任何調(diào)整,總時(shí)長在300s左右,當(dāng)然還有一些小地方可以做一些優(yōu)化。
如果需要這能關(guān)聯(lián)上的20萬條數(shù)據(jù),那么就先讓crcd與db關(guān)聯(lián)后再與其它表關(guān)聯(lián)生成最后的結(jié)果。
總結(jié)
1. outer join后如果還要繼續(xù)進(jìn)一步j(luò)oin,一定要注意關(guān)聯(lián)不上的數(shù)量,避免出現(xiàn)大量NULL而導(dǎo)致數(shù)據(jù)傾斜。
2. 建模時(shí)一定要考慮數(shù)據(jù)質(zhì)量。