orc表導緻hiveserver2記憶體暴漲問題分析
- orc表導緻hiveserver2記憶體暴漲問題分析
-
- 一、問題描述
- 二、解決過程
-
- 1.定位起因
- 2.分析sql
- 3.深入分析
- 三、orc檔案格式
- 四、問題驗證
- 五、解決方案
- 六、總結分析
一、問題描述
昨天上午,釘釘上突然出現一堆hive相關的查詢錯誤的報警。第一感覺,在yarn上檢視任務日志,查詢了一通,結果沒看到有任務相關的報錯。于是乎,立馬檢視hiveserver2的相關log,看到如下之類的資訊:
Detected pause in JVM or host machine (eg GC): pause of approximately 15290ms
GC pool 'ConcurrentMarkSweep' had collection(s): count=1 time=15778ms
大概的意思是由于gc,導緻hiveserver2整個服務停頓,stop the whole word!整整15秒不可用,對于軟體來說,是個毀滅性的災難!
為什麼會突然飙升呢?
又多方面的檢視hiveserver2的連接配接數監控
hive的連接配接數已經打滿了,log裡面也有thrift相關連接配接逾時的問題,提示連接配接池已滿,hive.server2.thrift.max.worker.threads預設數是100,表示同時處理thrift連接配接的線程數,這個時候調到500,繼續觀察,這時候突然看了hiveserver2 的記憶體使用情況
驚人地發現竟然記憶體滿了!!調整上面那參數并沒有解決問題。目前hiveserver2的記憶體是4g,于是趕緊把記憶體調到8g,記憶體翻倍後,本以為萬事大吉,調整後繼續觀察
不一會又跑滿了。是什麼鬼?hiveserver2又不做計算,為嘛要吃那麼多記憶體?感覺像是無底洞,這時hiveserver2挂了,一堆資料開發提着大刀在路上,不停地傳來“hive查詢不了啦” 的聲音,慌的一批。以上單純調記憶體并沒有真正解決問題,下面進入解決過程。
二、解決過程
1.定位起因
經過不停的看hiveserver2的log,找尋一些記憶體暴漲前的sql,經過地毯式地搜尋,定位到一條sql:
看似平平無奇的sql,為了複現,重新開機hiveserver2後,記憶體回歸正常,再次執行這條SQL,發現問題重新,記憶體再次暴漲,bingo,成功定位
2.分析sql
确定sql後,從這張表的特殊性開始下手,一查表結構
show create table temp_xq_ann_user_profile_20200326
竟然是個400多個字段的大寬表,而且是orc格式!注意,劃重點:orc,大寬表!
3.深入分析
hiveserver2記憶體這麼高?裡面到底是什麼東西呢?于是,我把jvm dump到本地,在hiveserver2的節點執行拿到pid,再用jmap把整個jvm dump到本地
ps -ef|grep hiveserver2
# 注意一定要在hiveserver2所運作的使用者下執行,這裡是hive使用者
su hive
jmap -dump:format=b,file=hiveserver2.hprof 13767
再導出hiveserver2.hprof到windows桌面,用jdk自帶的jvisualvm.exe工具打開,這個工具在JAVA_HOME/bin下可以找到,導入後可以清晰的看到對象占用的記憶體
我們可以看到
org.apache.orc.OrcProto$ColumnStatistics
類的執行個體數最多,占用堆記憶體最多。其次是byte和protobuf的類,這下有了初步的定位,确定了hiveserver2裡面占用記憶體的是什麼東西了。
三、orc檔案格式
這裡已經确定了orc相關的東西占用記憶體太多,是以必須先了解下orc檔案的結構
在ORC格式的hive表中,每個hdfs上的orc檔案會被橫向的切分為多個stripes,然後在每一個stripe内資料以列為機關進行存儲,所有列的内容都儲存在同一個檔案中。每個stripe的預設大小為64MB。ORC檔案也以二進制方式存儲的,是以是不可以直接讀取,ORC檔案也是自解析的,它包含許多的中繼資料,這些中繼資料都是通過ProtoBuffer進行序列化。除了藍色部分的主資料Raw Data之外,都可以稱作是metadata,這些都是通過pb序列化的。以下是metadata的protoc檔案定義:
message Metadata {
repeated StripeStatistics stripeStats = 1;
}
message StripeStatistics {
repeated ColumnStatistics colStats = 1;
}
message ColumnStatistics {
optional uint64 numberOfValues = 1;
optional IntegerStatistics intStatistics = 2;
optional DoubleStatistics doubleStatistics = 3;
optional StringStatistics stringStatistics = 4;
optional BucketStatistics bucketStatistics = 5;
optional DecimalStatistics decimalStatistics = 6;
optional DateStatistics dateStatistics = 7;
optional BinaryStatistics binaryStatistics = 8;
optional TimestampStatistics timestampStatistics = 9;
optional bool hasNull = 10;
}
message IntegerStatistics {
optional sint64 minimum = 1;
optional sint64 maximum = 2;
optional sint64 sum = 3;
}
message DoubleStatistics {
optional double minimum = 1;
optional double maximum = 2;
optional double sum = 3;
}
message StringStatistics {
optional string minimum = 1;
optional string maximum = 2;
// sum will store the total length of all strings in a stripe
optional sint64 sum = 3;
}
message BucketStatistics {
repeated uint64 count = 1 [packed=true];
}
message DecimalStatistics {
optional string minimum = 1;
optional string maximum = 2;
optional string sum = 3;
}
message DateStatistics {
// min,max values saved as days since epoch
optional sint32 minimum = 1;
optional sint32 maximum = 2;
}
message TimestampStatistics {
// min,max values saved as milliseconds since epoch
optional sint64 minimum = 1;
optional sint64 maximum = 2;
optional sint64 minimumUtc = 3;
optional sint64 maximumUtc = 4;
}
message BinaryStatistics {
// sum will store the total binary blob length in a stripe
optional sint64 sum = 1;
}
上面可以清晰地看到我們再jvm裡面分析的對象
ColumnStatistics
,同時也跟
com.google.protobuf.LiteralByteString
這個對象聯系起來,二者也就是上圖jvm堆記憶體中占用大量空間的罪魁禍首,裡面存了一個orc file中各個stripe中的各個column上的統計資訊,如最大值、最小值等等,如下圖
因為本身是400度個字段的大寬表,是以每個stripe存儲的column統計資訊會越多,平常字段不多的表可能問題無法凸顯,現在我們現在可以确定的是,一個orc檔案的stripe數量越多,需要存儲的統計資訊越多,也就是
ColumnStatistics
對象執行個體會越多,占用的記憶體空間會越大,即stripe數量與hiveserver2記憶體占用呈正相關
四、問題驗證
已經猜測是單個orc檔案的stripe數太多,我們來驗證一下,下面我從hive表對應的hdfs路徑拉取一個檔案來統計stripe數,這個檔案是440MB
hive --orcfiledump /user/lijf/data/test/000004_0 > statisc.txt
執行這條指令會把這個檔案的所有統計資訊存儲到本地檔案,裡面的内容也就是ColumnStatistics對象要存儲的東西,包括stripe的編号,我們來看看這個檔案總共多少個stripe
最大編号是1360,說明這個440MB的orc檔案裡面竟然驚人的有1360個stripe!,平均每個stripe 320kb左右,遠低于預設的64MB!嚴重的不科學!這裡也驗證了我上面的猜測是正确的
五、解決方案
- orc.stripe.size 的大小為64MB或更多,用戶端嚴格限制此參數+服務端限制此參數+建表時指定此參數 總不會再錯。
- set hive.exec.orc.split.strategy=BI; 設定這個參數會避免orc中繼資料緩存,預設參數本身是個優化,這裡取消掉
- hive.fetch.task.conversion=none 取消hive預設的優化,強制并行化執行
六、總結分析
這裡是用的hive查詢,一個普通的select *查詢一個orc大寬表,由于沒有走計算,是以全部資料的加載讀取索引的壓力全在hiveserver2的身上,orc作為列式存儲,它其中的一個的優勢是自帶的metadata資料,條件查詢的時候能夠加快索引,同時這也是它的一個緻命傷,為了快,必須要把這些中繼資料加載到記憶體中,如果是走計算的話比如select count(1),它能把計算和資料讀取分攤到各個節點,無壓力,偏偏是select * 的查詢不能分攤算力到各個節點,隻能hiveserver2去扛住,一旦metadata的資料量超過hiveserver2承受範圍就gg了。這個問題最後的解決方案很簡單,但定位過程卻相當磨人。前半段時間主要花在普通運維的基礎上去思考問題,後面才從orc的底層去思考,是以,思考問題的方向很重要,不然就是浪費時間