天天看點

Hive SQL優化之 Count Distinct

目錄

正常方式

失敗的優化

成功的優化

       Hive是Hadoop的子項目,它提供了對資料的結構化管理和類SQL語言的查詢功能。SQL的互動方式極大程度地降低了Hadoop生态環境中資料處理的門檻,使用者不需要編寫程式,通過SQL語句就可以對資料進行分析和處理。目前很多計算需求都可以由Hive來完成,極大程度地降低了開發成本。

       目前,Hive底層使用MapReduce作為實際計算架構,SQL的互動方式隐藏了大部分MapReduce的細節。這種細節的隐藏在帶來便利性的同時,也對計算作業的調優帶來了一定的難度。未經優化的SQL語句轉化後的MapReduce作業,它的運作效率可能大大低于使用者的預期。本文我們就來分析一個簡單語句的優化過程。

正常方式

       日常統計場景中,我們經常會對一段時期内的字段進行消重并統計數量,SQL語句類似于

SELECT COUNT( DISTINCT id ) FROM TABLE_NAME 
           

       這條語句是從一個表的符合WHERE條件的記錄中統計不重複的id的總數。

該語句轉化為MapReduce作業後執行示意圖如下,圖中還列出了我們實驗作業中Reduce階段的資料規模:

Hive SQL優化之 Count Distinct

       由于引入了DISTINCT,是以在Map階段無法利用combine對輸出結果消重,必須将id作為Key輸出,在Reduce階段再對來自于不同Map Task、相同Key的結果進行消重,計入最終統計值。

       我們看到作業運作時的Reduce Task個數為1,對于統計大資料量時,這會導緻最終Map的全部輸出由單個的ReduceTask處理。這唯一的Reduce Task需要Shuffle大量的資料,并且進行排序聚合等處理,這使得它成為整個作業的IO和運算瓶頸。

失敗的優化

       經過上述分析後,我們嘗試顯式地增大Reduce Task個數來提高Reduce階段的并發,使每一個Reduce Task的資料處理量控制在2G左右。具體設定如下:

set mapred.reduce.tasks=100
           

       調整後我們發現這一參數并沒有影響實際Reduce Task個數,Hive運作時輸出“Number of reduce tasks determined at compile time: 1”。原來Hive在處理COUNT這種“全聚合(full aggregates)”計算時,它會忽略使用者指定的Reduce Task數,而強制使用1。

成功的優化

       我們隻能采用變通的方法來繞過這一限制。我們利用Hive對嵌套語句的支援,将原來一個MapReduce作業轉換為兩個作業,在第一階段選出全部的非重複id,在第二階段再對這些已消重的id進行計數。這樣在第一階段我們可以通過增大Reduce的并發數,并發處理Map輸出。在第二階段,由于id已經消重,是以COUNT(*)操作在Map階段不需要輸出原id資料,隻輸出一個合并後的計數即可。這樣即使第二階段Hive強制指定一個Reduce Task,極少量的Map輸出資料也不會使單一的Reduce Task成為瓶頸。改進後的SQL語句如下:

SELECT COUNT(*) FROM (SELECT DISTINCT id FROM TABLE_NAME WHERE … ) t;
           

       在實際運作時,我們發現Hive還對這兩階段的作業做了額外的優化。它将第二個MapReduce作業Map中的Count過程移到了第一個作業的Reduce階段。這樣在第一階Reduce就可以輸出計數值,而不是消重的全部id。這一優化大幅地減少了第一個作業的Reduce輸出IO以及第二個作業Map的輸入資料量。最終在同樣的運作環境下優化後的語句執行隻需要原語句20%左右的時間。優化後的MapReduce作業流如下:

Hive SQL優化之 Count Distinct

       從上述優化過程我們可以看出,一個簡單的統計需求,如果不了解Hive和MapReduce的工作原理,它可能會比優化後的執行過程多四、五倍的時間。我們在利用Hive簡化開發的同時,也要盡可能優化SQL語句,提升計算作業的執行效率。

繼續閱讀