http://lxw1234.com/archives/2016/04/630.htm
關鍵字:orc、index、hive
Hive從0.11版本開始提供了ORC的文件格式,ORC文件不僅僅是一種列式文件存儲格式,最重要的是有着很高的壓縮比,並且對於MapReduce來說是可切分(Split)的。因此,在Hive中使用ORC作為表的文件存儲格式,不僅可以很大程度的節省HDFS存儲資源,而且對數據的查詢和處理性能有着非常大的提升,因為ORC較其他文件格式壓縮比高,查詢任務的輸入數據量減少,使用的Task也就減少了。關於Orc文件格式的官網介紹,見:
https://cwiki.apache.org/confluence/display/Hive/LanguageManual+ORC
需要注意的是,ORC能很大程序的節省存儲和計算資源,但它在讀寫時候需要消耗額外的CPU資源來壓縮和解壓縮,當然這部分的CPU消耗是非常少的。
對性能提升的另一個方面是通過在ORC文件中為每一個字段建立一個輕量級的索引,來判定一個文件中是否滿足WHERE子句中的過濾條件。比如:當執行HQL語句”SELECT COUNT(1) FROM lxw1234_orc WHERE id = 0”時候,先從ORC文件的metadata中讀取索引信息,快速定位到id=0所在的offsets,如果從索引信息中沒有發現id=0的信息,則直接跳過該文件。詳見后面介紹。
說明一下:本文使用Hive2.0.0 + hadoop-2.3.0-cdh5.0.0作為測試環境。
ORC的壓縮比

上圖中原始的TEXT文本文件為585GB,使用Hive早期的RCFILE壓縮后為505GB,使用Impala中的PARQUET壓縮后為221GB,而Hive中的ORC壓縮后僅為131GB,壓縮比最高。
查看ORC的文件元數據
先准備一張ORC的示例表:
- CREATE TABLE lxw1234_orc1 (
- id INT,
- name STRING
- ) stored AS ORC;
- INSERT overwrite TABLE lxw1234_orc1
- SELECT CAST(siteid AS INT) AS id,
- pcid
- FROM lxw1234_text
- limit 10;
- SELECT * FROM lxw1234_orc1 ORDER BY id;
- 139 89578071000037563815CC
- 139 E811C27809708556F87C79
- 633 82E0D8720C8D1556C75ABA
- 819 726B86DB00026B56F3F151
- 1134 8153CD6F059210539E4552
- 1154 5E26977B0EEE5456F7E7FB
- 1160 583C0271044D3D56F95436
- 1351 FA05CFDD05622756F953EE
- 1351 16A5707006C43356F95392
- 1361 3C17A17C076A7E56F87CCC
ORC表lxw1234_orc1對應的HDFS文件為:
/hivedata/warehouse2/lxw1234_orc1/000000_0
新版本的Hive中提供了更詳細的查看ORC文件信息的工具 orcfiledump。
執行命令:./hive –orcfiledump -j -p /hivedata/warehouse2/lxw1234_orc1/000000_0
返回一段JSON,將其格式化后:

schema

為每一個字段做了編號,從1開始,編號為0的columnId中描述了整個表的字段定義。
stripeStatistics

這里是ORC文件中所有stripes的統計信息,其中有每個stripe中每個字段的min/max值,是否有空值等等。
fileStatistics

這里是整個文件中每個字段的統計信息,該表只有一個文件,也只有一個stripe。
stripes
這里列出了所有stripes的元數據信息,包括index data, row data和stripe footer。
ORC查詢優化
經過上面ORC文件的元數據了解了一個ORC文件會被分成多個stripe,而且文件的元數據中有每個字段的統計信息(min/max,hasNull等等),這就為ORC的查詢優化做好了基礎准備。假如我的查詢過濾條件為WHERE id = 0;在Map Task讀到一個ORC文件時,首先從文件的統計信息中看看id字段的min/max值,如果0不包含在內,那么這個文件就可以直接跳過了。
基於這點,還有一個更有效的優化手段是在數據入庫的時候,根據id字段排序后入庫,這樣盡量能使id=0的數據位於同一個文件甚至是同一個stripe中,那么在查詢時候,只有負責讀取該文件的Map Task需要掃描文件,其他的Map Task都會跳過掃描,大大節省Map Task的執行時間。海量數據下,使用ORDER BY可能不太現實,另一個有效手段是使用DISTRIBUTE BY id SORT BY id;
使用下面的HQL構造一個較大的ORC表:
- CREATE TABLE lxw1234_orc2 stored AS ORC
- AS
- SELECT CAST(siteid AS INT) AS id,
- pcid
- FROM lxw1234_text
- DISTRIBUTE BY id sort BY id;
該語句保證相同的id位於同一個ORC文件中,並且是排序的。
SELECT DISTINCT INPUT__FILE__NAME FROM lxw1234_orc2 WHERE id = 0;
hdfs://cdh5/hivedata/warehouse2/lxw1234_orc2/000000_0
id=0的數據只存在於這一個文件中,而這個表有33個文件。

也可以通過命令
./hive –orcfiledump -j -p hdfs://cdh5/hivedata/warehouse2/lxw1234_orc2/000000_0
查看文件的統計信息:

該文件中id的最小值為0,最大值為1155.
因此,對於HQL查詢”SELECT COUNT(1) FROM lxw1234_orc2 WHERE id = 0”,優化器在執行時候,只會掃描這一個文件,其他文件都應該跳過。
在驗證之前,先介紹一個參數:
hive.optimize.index.filter,是否自動使用索引,默認為false(不使用);如果不設置該參數為true,那么ORC的索引當然也不會使用。
在Hive中執行set hive.optimize.index.filter=true;
SELECT COUNT(1) FROM lxw1234_orc2 WHERE id = 0;
查看日志,該查詢一共有13個MapTask,
找到包含/hivedata/warehouse2/lxw1234_orc2/000000_0的MapTask,查看日志:

查看其它MapTask,均沒有掃描記錄的日志。
不使用索引,再執行一次:
set hive.optimize.index.filter=false;
SELECT COUNT(1) FROM lxw1234_orc2 WHERE id = 0;
再查看日志時,每個MapTask中都有掃描記錄的日志,說明每個MapTask都對自己的分片進行了掃描。
兩次執行,MapTask的執行時間也能說明問題。
使用索引的耗時:

不使用索引的耗時(明顯多於上面):

由此可見,Hive中的ORC不僅僅有着高壓縮比,很大程序的節省存儲空間和計算資源,而且在其上還做了許多優化(這里僅僅介紹了row_index)。如果使用Hive作為大數據倉庫,強烈建議主要使用ORC文件格式作為表的存儲格式。
