我們知道,大部分Spark計算都是在記憶體中完成的,所以Spark的瓶頸一般來自於叢集(standalone, yarn, mesos, k8s)的資源緊張,CPU,網路頻寬,記憶體。Spark的效能,想要它快,就得充分利用好系統資源,尤其是記憶體和CPU。有時候我們也需要做一些優化調整來減少記憶體佔用,例如將小檔案進行合併的操作。
一、問題現象
我們有一個15萬條總資料量133MB的表,使用SELECT * FROM bi.dwd_tbl_conf_info全表查詢耗時3min,另外一個500萬條總資料量6.3G的表ods_tbl_conf_detail,查詢耗時23秒。兩張表均為列式儲存的表。
大表查詢快,而小表反而查詢慢了,為什麼會產生如此奇怪的現象呢?
二、問題探詢
資料量6.3G的表查詢耗時23秒,反而資料量133MB的小表查詢耗時3min,這非常奇怪。我們收集了對應的建表語句,發現兩者沒有太大的差異,大部分為String,兩表的列數也相差不大。
CREATE TABLE IF NOT EXISTS `bi`.`dwd_tbl_conf_info` (
`corp_id` STRING COMMENT '',
`dept_uuid` STRING COMMENT '',
`user_id` STRING COMMENT '',
`user_name` STRING COMMENT '',
`uuid` STRING COMMENT '',
`dtime` DATE COMMENT '',
`slice_number` INT COMMENT '',
`attendee_count` INT COMMENT '',
`mr_id` STRING COMMENT '',
`mr_pkg_id` STRING COMMENT '',
`mr_parties` INT COMMENT '',
`is_mr` TINYINT COMMENT 'R',
`is_live_conf` TINYINT COMMENT ''
)
CREATE TABLE IF NOT EXISTS `bi`.`ods_tbl_conf_detail` (
`id` string,
`conf_uuid` string,
`conf_id` string,
`name` string,
`number` string,
`device_type` string,
`j_time` bigint,
`l_time` bigint,
`media_type` string,
`dept_name` string,
`UPDATETIME` bigint,
`CREATETIME` bigint,
`user_id` string,
`USERAGENT` string,
`corp_id` string,
`account` string
)
因為兩張表均為很簡單的SELECT查詢操作,無任何複雜的聚合join操作,也無UDF相關的操作,所以基本確認查詢慢的應該發生的讀表的時候,我們將懷疑的點放到了讀表操作上。通過查詢兩個查詢語句的DAG和任務分佈,我們發現了不一樣的地方。
查詢快的表,查詢時總共有68個任務,任務分配比如均勻,平均7~9s左右,而查詢慢的表,查詢時總共1160個任務,平均也是9s左右。如下圖所示:
至此,我們基本發現了貓膩所在。大表6.3G但檔案個數小,只有68個,所以很快跑完了。而小表雖然只有133MB,但檔案個數特別多,導致產生的任務特別多,而由於單個任務本身比較快,大部分時間花費在任務排程上,導致任務耗時較長。
那如何才能解決小表查詢慢的問題呢?
三、業務調優
那現在擺在我們面前就存在現在問題:
1、為什麼小表會產生這麼小檔案
2、已經產生的這麼小檔案如何合併
帶著這兩個問題,我們和業務的開發人員聊了一個發現小表是業務開發人員從原始資料表中,按照不同的時間切片查詢並做資料清洗後插入到小表中的,而由於時間切片切的比較小,導致這樣的插入次數特別多,從而產生了大量的小檔案。
那麼我們需要解決的問題就是2個,如何才能把這些歷史的小檔案進行合併以及如何才能保證後續的業務流程中不再產生小檔案,我們指導業務開發人員做了以下優化:
1)使用INSERT OVERWRITE bi.dwd_tbl_conf_info SELECT * FROM bi.dwd_tbl_conf_info合併下歷史的資料。由於DLI做了資料一致性保護,OVERWRITE期間不影響原有資料的讀取和查詢,OVERWRITE之後就會使用新的合併後的資料。合併後全表查詢由原來的3min縮短到9s內完成。
2)原有表修改為分割槽表,插入時不同時間放入到不同分割槽,查詢時只查詢需要的時間段內的分割槽資料,進一步減小讀取資料量。