大數據:Parquet文件存儲格式
一、Parquet的組成
Parquet僅(jin)僅(jin)是一種存儲格式,它(ta)是語言、平臺無關的(de)(de),并且(qie)不需要(yao)和(he)任何一種數(shu)據(ju)處理框架綁定,目前能夠和(he)Parquet適配的(de)(de)組件(jian)包括下面這些,可以看出基本上通常使用的(de)(de)查詢引(yin)擎和(he)計算框架都已適配,并且(qie)可以很方便的(de)(de)將其它(ta)序列化工具生成的(de)(de)數(shu)據(ju)轉換成Parquet格式。
- 查詢引(yin)擎(qing): Hive, Impala, Pig, Presto, Drill, Tajo, HAWQ, IBM Big SQL
- 計算框架(jia): MapReduce, Spark, Cascading, Crunch, Scalding, Kite
- 數據模(mo)型: Avro, Thrift, Protocol Buffers, POJOs
項目組成
Parquet項(xiang)目由以下幾個(ge)子項(xiang)目組成:
- 項目由java實現,它(ta)定義了(le)所有Parquet元數據對象,Parquet的元數據是使用Apache Thrift進行序列化并存儲在Parquet文件的尾部(bu)。
- 項目由java實現(xian),它(ta)(ta)包括多個模(mo)塊,包括實現(xian)了讀(du)寫(xie)Parquet文件的(de)功能,并且(qie)提供一些和其(qi)它(ta)(ta)組(zu)件適(shi)配的(de)工具(ju),例如Hadoop Input/Output Formats、Hive Serde(目前Hive已(yi)經自帶Parquet了)、Pig loaders等。
- 項目(mu),包(bao)含不同編(bian)程語言之間(JAVA和C/C++)讀寫文(wen)件(jian)的測試代碼。
- 項目,它是用(yong)于用(yong)于讀(du)寫Parquet文件的C++庫。
下(xia)圖展(zhan)示(shi)了Parquet各個組件的層次以及從上到下(xia)交互的方(fang)式。

- 數(shu)(shu)據存儲層定義了(le)Parquet的文件(jian)格(ge)式,其(qi)中(zhong)元數(shu)(shu)據在parquet-format中(zhong)定義,包括Parquet原始類型(xing)定義、Page類型(xing)、編碼(ma)類型(xing)、壓縮類型(xing)等等。
- 對象轉換層(ceng)完成其他對象模(mo)型(xing)與Parquet內部(bu)數據模(mo)型(xing)的(de)映射和轉換,Parquet的(de)編碼方式使用的(de)是striping and assembly算法。
- 對象模型(xing)層(ceng)定義了(le)如(ru)何讀取Parquet文(wen)件(jian)的(de)內容,這一層(ceng)轉(zhuan)換(huan)包括Avro、Thrift、PB等(deng)(deng)序列(lie)化格(ge)式、Hive serde等(deng)(deng)的(de)適配。并(bing)且為了(le)幫助大家理解和使用,Parquet提供了(le)org.apache.parquet.example包實(shi)現了(le)java對象和Parquet文(wen)件(jian)的(de)轉(zhuan)換(huan)。
數據模型
Parquet支(zhi)持嵌(qian)套的數據(ju)(ju)模(mo)型(xing)(xing),類(lei)似(si)于Protocol Buffers,每一個(ge)數據(ju)(ju)模(mo)型(xing)(xing)的schema包含(han)多個(ge)字(zi)段(duan)(duan),每一個(ge)字(zi)段(duan)(duan)又可以(yi)(yi)(yi)包含(han)多個(ge)字(zi)段(duan)(duan),每一個(ge)字(zi)段(duan)(duan)有(you)三個(ge)屬性:重復數、數據(ju)(ju)類(lei)型(xing)(xing)和字(zi)段(duan)(duan)名,重復數可以(yi)(yi)(yi)是以(yi)(yi)(yi)下三種:required(出(chu)現(xian)(xian)1次(ci)(ci)(ci)),repeated(出(chu)現(xian)(xian)0次(ci)(ci)(ci)或多次(ci)(ci)(ci)),optional(出(chu)現(xian)(xian)0次(ci)(ci)(ci)或1次(ci)(ci)(ci))。每一個(ge)字(zi)段(duan)(duan)的數據(ju)(ju)類(lei)型(xing)(xing)可以(yi)(yi)(yi)分(fen)成兩種:group(復雜類(lei)型(xing)(xing))和primitive(基本(ben)類(lei)型(xing)(xing))。例(li)如(ru)(ru)Dremel中提(ti)供的Document的schema示例(li),它的定義如(ru)(ru)下:
message Document {
required int64 DocId;
optional group Links {
repeated int64 Backward;
repeated int64 Forward;
}
repeated group Name {
repeated group Language {
required string Code;
optional string Country;
}
optional string Url;
}
}
可(ke)以(yi)把這個Schema轉換成樹狀(zhuang)結構,根節點(dian)可(ke)以(yi)理解(jie)為repeated類型(xing),如下圖(tu):

可以(yi)看(kan)出(chu)在(zai)Schema中所有的基本類型字段(duan)都是葉子(zi)節點,在(zai)這(zhe)(zhe)個(ge)(ge)Schema中一共存在(zai)6個(ge)(ge)葉子(zi)節點,如果把這(zhe)(zhe)樣(yang)的Schema轉換成扁平式的關(guan)系模型,就可以(yi)理解(jie)為該表(biao)(biao)包含(han)六個(ge)(ge)列(lie)。Parquet中沒(mei)有Map、Array這(zhe)(zhe)樣(yang)的復(fu)雜數據結構(gou),但(dan)是可以(yi)通過repeated和(he)group組合來(lai)實現(xian)這(zhe)(zhe)樣(yang)的需求。在(zai)這(zhe)(zhe)個(ge)(ge)包含(han)6個(ge)(ge)字段(duan)的表(biao)(biao)中有以(yi)下幾(ji)個(ge)(ge)字段(duan)和(he)每一條(tiao)記錄中它們可能出(chu)現(xian)的次數:
DocId int64 只(zhi)能出現一(yi)次(ci)
Links.Backward int64 可能出現任意多次(ci),但是如果(guo)出現0次(ci)則需要使用NULL標識
Links.Forward int64 同上
Name.Language.Code string 同(tong)上(shang)
Name.Language.Country string 同(tong)上(shang)
Name.Url string 同上
由于在(zai)(zai)一個表(biao)中(zhong)可能存在(zai)(zai)出現任意多次的列(lie),對于這些(xie)列(lie)需要標示出現多次或者(zhe)等于NULL的情況(kuang),它是由Striping/Assembly算法實現的。
Striping/Assembly算法
上文(wen)介紹了Parquet的(de)數據(ju)模型,在Document中存在多個(ge)非(fei)required列,由于Parquet一條記錄的(de)數據(ju)分(fen)(fen)散的(de)存儲在不同的(de)列中,如(ru)何組(zu)合(he)不同的(de)列值組(zu)成一條記錄是由Striping/Assembly算(suan)法決定的(de),在該算(suan)法中列的(de)每一個(ge)值都包含三(san)部(bu)分(fen)(fen):value、repetition level和definition level。
Repetition Levels
為了支持repeated類型的(de)(de)(de)節(jie)點,在寫(xie)入的(de)(de)(de)時候(hou)該值(zhi)(zhi)等于它(ta)和前面的(de)(de)(de)值(zhi)(zhi)在哪(na)一層節(jie)點是(shi)不(bu)共享的(de)(de)(de)。在讀取(qu)的(de)(de)(de)時候(hou)根(gen)據該值(zhi)(zhi)可(ke)以推導(dao)出(chu)哪(na)一層上需要創(chuang)建一個新的(de)(de)(de)節(jie)點,例(li)如對于這樣的(de)(de)(de)一個schema和兩條記錄。
message nested {
repeated group leve1 {
repeated string leve2;
}
}
r1:[[a,b,c,] , [d,e,f,g]]
r2:[[h] , [i,j]]
計算repetition level值的過程如下:
- value=a是(shi)一條記錄(lu)的(de)開始,和(he)前面(mian)的(de)值(已經(jing)沒有值了)在(zai)根(gen)節點(第0層)上(shang)是(shi)不共享的(de),所以repeated level=0.
- value=b它和前(qian)面的值共享(xiang)了level1這個節點,但是level2這個節點上是不(bu)共享(xiang)的,所以repeated level=2.
- 同理value=c, repeated level=2.
- value=d和前面的值(zhi)共(gong)享了根節(jie)點(屬(shu)于相同(tong)記錄(lu)),但是在(zai)level1這個節(jie)點上(shang)是不共(gong)享的,所(suo)以repeated level=1.
- value=h和前面的值不屬于同一條(tiao)記錄,也就是不共(gong)享任何節點,所以repeated level=0.
根據以上的分析每一個value需要記錄的repeated level值如下(xia):

在讀取的(de)(de)(de)時候,順序的(de)(de)(de)讀取每一(yi)個值,然(ran)后(hou)根據它的(de)(de)(de)repeated level創(chuang)(chuang)建(jian)對(dui)象,當(dang)讀取value=a時repeated level=0,表(biao)示需(xu)要創(chuang)(chuang)建(jian)一(yi)個新的(de)(de)(de)根節(jie)點(新記(ji)錄(lu)(lu)),value=b時repeated level=2,表(biao)示需(xu)要創(chuang)(chuang)建(jian)一(yi)個新的(de)(de)(de)level2節(jie)點,value=d時repeated level=1,表(biao)示需(xu)要創(chuang)(chuang)建(jian)一(yi)個新的(de)(de)(de)level1節(jie)點,當(dang)所有列讀取完成(cheng)之(zhi)后(hou)可以創(chuang)(chuang)建(jian)一(yi)條新的(de)(de)(de)記(ji)錄(lu)(lu)。本例中當(dang)讀取文件構建(jian)每條記(ji)錄(lu)(lu)的(de)(de)(de)結果(guo)如下:

可以看出repeated level=0表示一(yi)(yi)條記錄的(de)(de)(de)(de)開始,并(bing)且(qie)repeated level的(de)(de)(de)(de)值(zhi)只是針對路(lu)徑(jing)上(shang)的(de)(de)(de)(de)repeated類(lei)型的(de)(de)(de)(de)節點(dian),因此(ci)在(zai)計算該(gai)值(zhi)的(de)(de)(de)(de)時(shi)候可以忽略非repeated類(lei)型的(de)(de)(de)(de)節點(dian),在(zai)寫入的(de)(de)(de)(de)時(shi)候將其理解為該(gai)節點(dian)和路(lu)徑(jing)上(shang)的(de)(de)(de)(de)哪一(yi)(yi)個repeated節點(dian)是不共享的(de)(de)(de)(de),讀取的(de)(de)(de)(de)時(shi)候將其理解為需要(yao)在(zai)哪一(yi)(yi)層(ceng)創(chuang)建一(yi)(yi)個新的(de)(de)(de)(de)repeated節點(dian),這樣的(de)(de)(de)(de)話(hua)每一(yi)(yi)列最(zui)大的(de)(de)(de)(de)repeated level值(zhi)就等于路(lu)徑(jing)上(shang)的(de)(de)(de)(de)repeated節點(dian)的(de)(de)(de)(de)個數(不包括根節點(dian))。減小repeated level的(de)(de)(de)(de)好處能夠使得(de)在(zai)存(cun)儲使用更加緊湊的(de)(de)(de)(de)編碼方式,節省(sheng)存(cun)儲空間。
Definition Levels
有了repeated level我們(men)(men)就可(ke)以構造出(chu)一個(ge)記錄了,為什么(me)還需要definition levels呢?由于repeated和optional類型(xing)的(de)(de)存在,可(ke)能一條記錄中某一列是(shi)沒有值的(de)(de),假(jia)設(she)我們(men)(men)不記錄這(zhe)樣(yang)的(de)(de)值就會導(dao)致本該屬(shu)于下一條記錄的(de)(de)值被(bei)當做當前(qian)記錄的(de)(de)一部分,從而造成數據(ju)的(de)(de)錯誤,因此對于這(zhe)種(zhong)情(qing)況需要一個(ge)占(zhan)位符標(biao)示這(zhe)種(zhong)情(qing)況。
definition level的(de)(de)值僅(jin)僅(jin)對于(yu)空值是有(you)效的(de)(de),表示在該值的(de)(de)路(lu)徑上第幾層開始是未定義(yi)(yi)(yi)的(de)(de),對于(yu)非空的(de)(de)值它是沒有(you)意義(yi)(yi)(yi)的(de)(de),因為(wei)非空值在葉子節(jie)點是定義(yi)(yi)(yi)的(de)(de),所(suo)有(you)的(de)(de)父節(jie)點也肯定是定義(yi)(yi)(yi)的(de)(de),因此(ci)它總是等于(yu)該列最大(da)的(de)(de)definition levels。例(li)如下面的(de)(de)schema。
message ExampleDefinitionLevel {
optional group a {
optional group b {
optional string c;
}
}
}
它包含一個(ge)(ge)列(lie)a.b.c,這個(ge)(ge)列(lie)的(de)的(de)每(mei)一個(ge)(ge)節點都(dou)是(shi)optional類型(xing)的(de),當c被定(ding)義(yi)(yi)時(shi)a和b肯定(ding)都(dou)是(shi)已(yi)定(ding)義(yi)(yi)的(de),當c未定(ding)義(yi)(yi)時(shi)我們就(jiu)需要標示出在從哪一層開始時(shi)未定(ding)義(yi)(yi)的(de),如(ru)下(xia)面的(de)值:

由于(yu)definition level只(zhi)需要(yao)考慮未定(ding)(ding)義的值(zhi)(zhi),而對于(yu)repeated類型的節(jie)(jie)點(dian),只(zhi)要(yao)父(fu)節(jie)(jie)點(dian)是已(yi)定(ding)(ding)義的,該(gai)節(jie)(jie)點(dian)就必須定(ding)(ding)義(例如(ru)Document中(zhong)的DocId,每一條(tiao)記(ji)錄都該(gai)列都必須有值(zhi)(zhi),同樣(yang)對于(yu)Language節(jie)(jie)點(dian),只(zhi)要(yao)它定(ding)(ding)義了(le)Code必須有值(zhi)(zhi)),所以計算definition level的值(zhi)(zhi)時(shi)可(ke)以忽略路徑上的required節(jie)(jie)點(dian),這樣(yang)可(ke)以減小definition level的最(zui)大值(zhi)(zhi),優化存儲。
一個完整的例子
本(ben)節我們使用(yong)Dremel論(lun)文(wen)中給的(de)Document示例和給定(ding)(ding)的(de)兩個值r1和r2展示計算repeated level和definition level的(de)過程(cheng),這里把未定(ding)(ding)義的(de)值記錄為(wei)NULL,使用(yong)R表示repeated level,D表示definition level。

首(shou)先看DocuId這一列,對(dui)于r1,DocId=10,由于它是(shi)記(ji)錄的開始并且是(shi)已定義的,所以R=0,D=0,同樣r2中的DocId=20,R=0,D=0。
對于(yu)Links.Forward這一(yi)列,在r1中(zhong),它是未定義的但是Links是已定義的,并且是該記(ji)錄(lu)(lu)中(zhong)的第一(yi)個值,所以R=0,D=1,在r1中(zhong)該列有兩個值,value1=10,R=0(記(ji)錄(lu)(lu)中(zhong)該列的第一(yi)個值),D=2(該列的最大(da)definition level)。
對(dui)于Name.Url這一(yi)列,r1中(zhong)(zhong)它(ta)有三(san)個(ge)(ge)(ge)值(zhi),分別為url1=’‘,它(ta)是(shi)r1中(zhong)(zhong)該列的第一(yi)個(ge)(ge)(ge)值(zhi)并且(qie)是(shi)定義的,所以(yi)R=0,D=2;value2=’‘,和上一(yi)個(ge)(ge)(ge)值(zhi)value1在Name這一(yi)層(ceng)是(shi)不相同的,所以(yi)R=1,D=2;value3=NULL,和上一(yi)個(ge)(ge)(ge)值(zhi)value2在Name這一(yi)層(ceng)是(shi)不相同的,所以(yi)R=1,但它(ta)是(shi)未定義的,而Name這一(yi)層(ceng)是(shi)定義的,所以(yi)D=1。r2中(zhong)(zhong)該列只有一(yi)個(ge)(ge)(ge)值(zhi)value3=’‘,R=0,D=2.
最后看一(yi)(yi)(yi)下Name.Language.Code這一(yi)(yi)(yi)列(lie),r1中(zhong)(zhong)有4個(ge)(ge)(ge)(ge)值,value1=’en-us’,它是r1中(zhong)(zhong)的(de)(de)第一(yi)(yi)(yi)個(ge)(ge)(ge)(ge)值并且是已(yi)定義(yi)(yi)的(de)(de),所(suo)以(yi)(yi)R=0,D=2(由于Code是required類型,這一(yi)(yi)(yi)列(lie)repeated level的(de)(de)最大(da)值等于2);value2=’en’,它和value1在(zai)(zai)(zai)Language這個(ge)(ge)(ge)(ge)節(jie)點(dian)是不共享的(de)(de),所(suo)以(yi)(yi)R=2,D=2;value3=NULL,它是未(wei)定義(yi)(yi)的(de)(de),但(dan)是它和前一(yi)(yi)(yi)個(ge)(ge)(ge)(ge)值在(zai)(zai)(zai)Name這個(ge)(ge)(ge)(ge)節(jie)點(dian)是不共享的(de)(de),在(zai)(zai)(zai)Name這個(ge)(ge)(ge)(ge)節(jie)點(dian)是已(yi)定義(yi)(yi)的(de)(de),所(suo)以(yi)(yi)R=1,D=1;value4=’en-gb’,它和前一(yi)(yi)(yi)個(ge)(ge)(ge)(ge)值在(zai)(zai)(zai)Name這一(yi)(yi)(yi)層不共享,所(suo)以(yi)(yi)R=1,D=2。在(zai)(zai)(zai)r2中(zhong)(zhong)該列(lie)有一(yi)(yi)(yi)個(ge)(ge)(ge)(ge)值,它是未(wei)定義(yi)(yi)的(de)(de),但(dan)是Name這一(yi)(yi)(yi)層是已(yi)定義(yi)(yi)的(de)(de),所(suo)以(yi)(yi)R=0,D=1.
Parquet文件格式
Parquet文(wen)件(jian)(jian)是(shi)(shi)以二進制方式(shi)存儲的(de)(de),所以是(shi)(shi)不(bu)可以直接讀取的(de)(de),文(wen)件(jian)(jian)中(zhong)包括該文(wen)件(jian)(jian)的(de)(de)數據和元數據,因此Parquet格式(shi)文(wen)件(jian)(jian)是(shi)(shi)自解析的(de)(de)。在(zai)HDFS文(wen)件(jian)(jian)系統和Parquet文(wen)件(jian)(jian)中(zhong)存在(zai)如下(xia)幾個概(gai)念。
- HDFS塊(Block):它是HDFS上(shang)的(de)(de)最小的(de)(de)副本(ben)單位,HDFS會把(ba)一(yi)(yi)個Block存儲在本(ben)地的(de)(de)一(yi)(yi)個文件(jian)并且維護分散在不同(tong)的(de)(de)機(ji)器(qi)上(shang)的(de)(de)多個副本(ben),通常情況下一(yi)(yi)個Block的(de)(de)大小為256M、512M等。
- HDFS文(wen)件(File):一個HDFS的文(wen)件,包括數(shu)據(ju)和(he)元(yuan)數(shu)據(ju),數(shu)據(ju)分散存儲在(zai)多個Block中。
- 行組(zu)(Row Group):按(an)照行將(jiang)數據物理上劃分為多個單(dan)元(yuan),每一(yi)個行組(zu)包含一(yi)定(ding)的(de)(de)(de)行數,在(zai)一(yi)個HDFS文(wen)件中至(zhi)少存(cun)儲一(yi)個行組(zu),Parquet讀寫的(de)(de)(de)時(shi)候會將(jiang)整個行組(zu)緩存(cun)在(zai)內存(cun)中,所以如果(guo)每一(yi)個行組(zu)的(de)(de)(de)大小(xiao)是(shi)由內存(cun)大的(de)(de)(de)小(xiao)決定(ding)的(de)(de)(de),例如記錄占用空間(jian)比較(jiao)小(xiao)的(de)(de)(de)Schema可以在(zai)每一(yi)個行組(zu)中存(cun)儲更(geng)多的(de)(de)(de)行。
- 列(lie)(lie)(lie)塊(Column Chunk):在一(yi)(yi)(yi)個(ge)行(xing)(xing)組中每一(yi)(yi)(yi)列(lie)(lie)(lie)保存在一(yi)(yi)(yi)個(ge)列(lie)(lie)(lie)塊中,行(xing)(xing)組中的所有列(lie)(lie)(lie)連續的存儲在這個(ge)行(xing)(xing)組文件(jian)中。一(yi)(yi)(yi)個(ge)列(lie)(lie)(lie)塊中的值(zhi)都是相同(tong)類(lei)型的,不(bu)同(tong)的列(lie)(lie)(lie)塊可能使用不(bu)同(tong)的算法進行(xing)(xing)壓縮。
- 頁(Page):每一個(ge)列塊(kuai)劃分為(wei)多個(ge)頁,一個(ge)頁是最小的(de)編碼的(de)單位,在同一個(ge)列塊(kuai)的(de)不(bu)同頁可能(neng)使用不(bu)同的(de)編碼方式。
文件格式
通常情(qing)況(kuang)下,在(zai)存(cun)儲Parquet數據的時候會按照Block大(da)(da)小設置(zhi)行組(zu)的大(da)(da)小,由(you)于一(yi)般(ban)情(qing)況(kuang)下每(mei)(mei)一(yi)個Mapper任(ren)務(wu)處理(li)數據的最小單(dan)位是(shi)一(yi)個Block,這(zhe)樣(yang)可以(yi)把每(mei)(mei)一(yi)個行組(zu)由(you)一(yi)個Mapper任(ren)務(wu)處理(li),增大(da)(da)任(ren)務(wu)執行并行度。Parquet文件的格式如下圖所示

上圖展示了(le)一(yi)(yi)個(ge)Parquet文(wen)(wen)(wen)件(jian)(jian)(jian)(jian)(jian)的(de)(de)(de)(de)內容(rong),一(yi)(yi)個(ge)文(wen)(wen)(wen)件(jian)(jian)(jian)(jian)(jian)中(zhong)(zhong)可以(yi)存(cun)(cun)儲(chu)(chu)多(duo)個(ge)行(xing)組(zu),文(wen)(wen)(wen)件(jian)(jian)(jian)(jian)(jian)的(de)(de)(de)(de)首位都是(shi)(shi)該(gai)(gai)文(wen)(wen)(wen)件(jian)(jian)(jian)(jian)(jian)的(de)(de)(de)(de)Magic Code,用(yong)于校驗它是(shi)(shi)否是(shi)(shi)一(yi)(yi)個(ge)Parquet文(wen)(wen)(wen)件(jian)(jian)(jian)(jian)(jian),Footer length了(le)文(wen)(wen)(wen)件(jian)(jian)(jian)(jian)(jian)元(yuan)數(shu)據(ju)(ju)的(de)(de)(de)(de)大小,通過該(gai)(gai)值(zhi)和(he)文(wen)(wen)(wen)件(jian)(jian)(jian)(jian)(jian)長度可以(yi)計算出元(yuan)數(shu)據(ju)(ju)的(de)(de)(de)(de)偏移量,文(wen)(wen)(wen)件(jian)(jian)(jian)(jian)(jian)的(de)(de)(de)(de)元(yuan)數(shu)據(ju)(ju)中(zhong)(zhong)包括每(mei)一(yi)(yi)個(ge)行(xing)組(zu)的(de)(de)(de)(de)元(yuan)數(shu)據(ju)(ju)信息和(he)該(gai)(gai)文(wen)(wen)(wen)件(jian)(jian)(jian)(jian)(jian)存(cun)(cun)儲(chu)(chu)數(shu)據(ju)(ju)的(de)(de)(de)(de)Schema信息。除(chu)了(le)文(wen)(wen)(wen)件(jian)(jian)(jian)(jian)(jian)中(zhong)(zhong)每(mei)一(yi)(yi)個(ge)行(xing)組(zu)的(de)(de)(de)(de)元(yuan)數(shu)據(ju)(ju),每(mei)一(yi)(yi)頁(ye)(ye)(ye)(ye)的(de)(de)(de)(de)開始都會存(cun)(cun)儲(chu)(chu)該(gai)(gai)頁(ye)(ye)(ye)(ye)的(de)(de)(de)(de)元(yuan)數(shu)據(ju)(ju),在Parquet中(zhong)(zhong),有三種(zhong)類型的(de)(de)(de)(de)頁(ye)(ye)(ye)(ye):數(shu)據(ju)(ju)頁(ye)(ye)(ye)(ye)、字(zi)(zi)典頁(ye)(ye)(ye)(ye)和(he)索引(yin)頁(ye)(ye)(ye)(ye)。數(shu)據(ju)(ju)頁(ye)(ye)(ye)(ye)用(yong)于存(cun)(cun)儲(chu)(chu)當(dang)前行(xing)組(zu)中(zhong)(zhong)該(gai)(gai)列(lie)的(de)(de)(de)(de)值(zhi),字(zi)(zi)典頁(ye)(ye)(ye)(ye)存(cun)(cun)儲(chu)(chu)該(gai)(gai)列(lie)值(zhi)的(de)(de)(de)(de)編(bian)碼(ma)字(zi)(zi)典,每(mei)一(yi)(yi)個(ge)列(lie)塊中(zhong)(zhong)最多(duo)包含一(yi)(yi)個(ge)字(zi)(zi)典頁(ye)(ye)(ye)(ye),索引(yin)頁(ye)(ye)(ye)(ye)用(yong)來存(cun)(cun)儲(chu)(chu)當(dang)前行(xing)組(zu)下該(gai)(gai)列(lie)的(de)(de)(de)(de)索引(yin),目前Parquet中(zhong)(zhong)還不支(zhi)持索引(yin)頁(ye)(ye)(ye)(ye),但是(shi)(shi)在后面的(de)(de)(de)(de)版本中(zhong)(zhong)增加。
在(zai)執行MR任(ren)務(wu)的(de)時候可能存在(zai)多個(ge)(ge)Mapper任(ren)務(wu)的(de)輸入是(shi)同一個(ge)(ge)Parquet文件的(de)情況,每一個(ge)(ge)Mapper通(tong)過InputSplit標示處理的(de)文件范(fan)圍,如果多個(ge)(ge)InputSplit跨越了(le)一個(ge)(ge)Row Group,Parquet能夠保證一個(ge)(ge)Row Group只(zhi)會被一個(ge)(ge)Mapper任(ren)務(wu)處理。
映射下推(Project PushDown)
說到列(lie)式(shi)存(cun)儲的(de)優勢,映(ying)射下推是(shi)最(zui)突出(chu)的(de),它意味著在獲取(qu)表中原始數據時只需要掃描查詢中需要的(de)列(lie),由于每一列(lie)的(de)所有值都(dou)是(shi)連續存(cun)儲的(de),所以(yi)分(fen)區取(qu)出(chu)每一列(lie)的(de)所有值就可(ke)以(yi)實現TableScan算子,而避免掃描整個(ge)表文件內(nei)容。
在Parquet中原生(sheng)就(jiu)支持映射下推,執行查詢的(de)(de)時候可(ke)以通過Configuration傳遞(di)需要讀(du)取(qu)的(de)(de)列的(de)(de)信息(xi),這(zhe)些列必(bi)須是Schema的(de)(de)子(zi)集,映射每次(ci)(ci)(ci)會(hui)掃描(miao)一個Row Group的(de)(de)數(shu)據(ju),然后一次(ci)(ci)(ci)性得將該Row Group里(li)所有需要的(de)(de)列的(de)(de)Cloumn Chunk都讀(du)取(qu)到(dao)內存(cun)中,每次(ci)(ci)(ci)讀(du)取(qu)一個Row Group的(de)(de)數(shu)據(ju)能夠大大降低隨機讀(du)的(de)(de)次(ci)(ci)(ci)數(shu),除(chu)此之外(wai),Parquet在讀(du)取(qu)的(de)(de)時候會(hui)考慮列是否連續(xu),如(ru)果(guo)某(mou)些需要的(de)(de)列是存(cun)儲位置是連續(xu)的(de)(de),那么一次(ci)(ci)(ci)讀(du)操作就(jiu)可(ke)以把多(duo)個列的(de)(de)數(shu)據(ju)讀(du)取(qu)到(dao)內存(cun)。
謂詞下推(Predicate PushDown)
在數(shu)(shu)據庫之(zhi)類的(de)(de)查詢(xun)系統中最(zui)(zui)常(chang)用的(de)(de)優化手段就是謂詞下推了,通過將一(yi)些過濾條件盡(jin)可能的(de)(de)在最(zui)(zui)底層(ceng)執(zhi)行(xing)(xing)可以減少每一(yi)層(ceng)交(jiao)互的(de)(de)數(shu)(shu)據量,從而(er)提升性(xing)能,例如(ru)(ru)”select count(1) from A Join B on A.id = B.id where A.a > 10 and B.b < 100”SQL查詢(xun)中,在處理Join操(cao)(cao)作(zuo)之(zhi)前需要首先對A和B執(zhi)行(xing)(xing)TableScan操(cao)(cao)作(zuo),然(ran)后(hou)再進行(xing)(xing)Join,再執(zhi)行(xing)(xing)過濾,最(zui)(zui)后(hou)計算聚(ju)合函(han)數(shu)(shu)返回(hui),但(dan)是如(ru)(ru)果把過濾條件A.a > 10和B.b < 100分別移到A表的(de)(de)TableScan和B表的(de)(de)TableScan的(de)(de)時(shi)候執(zhi)行(xing)(xing),可以大(da)大(da)降低Join操(cao)(cao)作(zuo)的(de)(de)輸入(ru)數(shu)(shu)據。
無論是(shi)行式存(cun)儲還是(shi)列(lie)式存(cun)儲,都(dou)可(ke)以(yi)在將過(guo)濾(lv)條件(jian)在讀取一(yi)(yi)條記(ji)(ji)錄(lu)之后執行以(yi)判(pan)斷該記(ji)(ji)錄(lu)是(shi)否(fou)需要返回給調用者,在Parquet做了(le)更進一(yi)(yi)步的(de)優化(hua),優化(hua)的(de)方法時對每(mei)一(yi)(yi)個(ge)Row Group的(de)每(mei)一(yi)(yi)個(ge)Column Chunk在存(cun)儲的(de)時候都(dou)計(ji)算對應的(de)統計(ji)信息,包(bao)括該Column Chunk的(de)最(zui)大值(zhi)(zhi)(zhi)、最(zui)小值(zhi)(zhi)(zhi)和空(kong)值(zhi)(zhi)(zhi)個(ge)數。通過(guo)這些統計(ji)值(zhi)(zhi)(zhi)和該列(lie)的(de)過(guo)濾(lv)條件(jian)可(ke)以(yi)判(pan)斷該Row Group是(shi)否(fou)需要掃(sao)描(miao)。另外Parquet未來還會增加(jia)諸如Bloom Filter和Index等優化(hua)數據(ju),更加(jia)有效的(de)完成謂(wei)詞下(xia)推。
在使用(yong)(yong)Parquet的(de)時(shi)候可(ke)以通過如下兩種策略提升查詢性能:1、類似于(yu)關系數(shu)據庫(ku)的(de)主鍵(jian),對需要頻繁過濾的(de)列(lie)設置為有序的(de),這(zhe)樣在導入數(shu)據的(de)時(shi)候會根(gen)據該列(lie)的(de)順序存儲數(shu)據,這(zhe)樣可(ke)以最大(da)化的(de)利用(yong)(yong)最大(da)值、最小(xiao)值實(shi)現謂詞下推。2、減小(xiao)行(xing)組(zu)大(da)小(xiao)和(he)頁大(da)小(xiao),這(zhe)樣增加跳過整個行(xing)組(zu)的(de)可(ke)能性,但是此時(shi)需要權衡由(you)于(yu)壓縮和(he)編碼效(xiao)率下降帶來(lai)的(de)I/O負載(zai)。
性能
相(xiang)比(bi)傳統的(de)(de)(de)(de)行式存儲,Hadoop生(sheng)態(tai)圈近年來也涌現出諸如RC、ORC、Parquet的(de)(de)(de)(de)列式存儲格式,它們的(de)(de)(de)(de)性能(neng)(neng)優(you)勢主要體現在兩個方面:1、更(geng)高(gao)的(de)(de)(de)(de)壓縮(suo)比(bi),由于相(xiang)同類型的(de)(de)(de)(de)數據(ju)更(geng)容易針對(dui)不同類型的(de)(de)(de)(de)列使用高(gao)效的(de)(de)(de)(de)編碼和壓縮(suo)方式。2、更(geng)小(xiao)的(de)(de)(de)(de)I/O操作(zuo),由于映(ying)射下推和謂詞(ci)下推的(de)(de)(de)(de)使用,可以減少一大(da)部分(fen)不必要的(de)(de)(de)(de)數據(ju)掃描,尤其(qi)是表結構(gou)比(bi)較龐大(da)的(de)(de)(de)(de)時候更(geng)加明顯,由此(ci)也能(neng)(neng)夠帶來更(geng)好(hao)的(de)(de)(de)(de)查詢性能(neng)(neng)

上(shang)圖是(shi)展示了(le)使(shi)用不同格式(shi)存儲(chu)TPC-H和TPC-DS數(shu)據集中兩個表數(shu)據的(de)(de)文件(jian)(jian)大小(xiao)對比,可(ke)以(yi)看(kan)出Parquet較(jiao)之于其他(ta)的(de)(de)二進制文件(jian)(jian)存儲(chu)格式(shi)能夠(gou)更有效(xiao)的(de)(de)利用存儲(chu)空間,而新版本的(de)(de)Parquet(2.0版本)使(shi)用了(le)更加(jia)高效(xiao)的(de)(de)頁存儲(chu)方式(shi),進一步的(de)(de)提升存儲(chu)空間

上圖(tu)展示了(le)Twitter在Impala中使用不同格(ge)(ge)式文件執(zhi)行TPC-DS基準測(ce)試的結果(guo),測(ce)試結果(guo)可(ke)以看出Parquet較之于其他的行式存儲格(ge)(ge)式有(you)較明(ming)顯的性能提升。

上圖展示(shi)了criteo公(gong)司(si)在Hive中使用(yong)ORC和Parquet兩(liang)種(zhong)列式(shi)存(cun)儲格(ge)式(shi)執行TPC-DS基準(zhun)測試的結(jie)果(guo),測試結(jie)果(guo)可以看出在數據存(cun)儲方面,兩(liang)種(zhong)存(cun)儲格(ge)式(shi)在都是(shi)用(yong)snappy壓(ya)縮的情況下(xia)量中存(cun)儲格(ge)式(shi)占用(yong)的空間相差并不(bu)大,查(cha)詢(xun)的結(jie)果(guo)顯示(shi)Parquet格(ge)式(shi)稍好于ORC格(ge)式(shi),兩(liang)者在功能上也(ye)(ye)都有優缺點,Parquet原生支(zhi)(zhi)持(chi)嵌套式(shi)數據結(jie)構,而(er)ORC對(dui)此支(zhi)(zhi)持(chi)的較差,這種(zhong)復(fu)雜的Schema查(cha)詢(xun)也(ye)(ye)相對(dui)較差;而(er)Parquet不(bu)支(zhi)(zhi)持(chi)數據的修改和ACID,但(dan)是(shi)ORC對(dui)此提供支(zhi)(zhi)持(chi),但(dan)是(shi)在OLAP環(huan)境下(xia)很少會對(dui)單條數據修改,更多(duo)的則是(shi)批量導(dao)入。
項目發展
自從2012年(nian)由Twitter和Cloudera共(gong)同研發(fa)(fa)Parquet開(kai)始(shi),該(gai)項(xiang)目(mu)一直處(chu)于高速發(fa)(fa)展之中,并且在項(xiang)目(mu)之初就將其(qi)貢獻給(gei)開(kai)源社區,2013年(nian),Criteo公司加(jia)入開(kai)發(fa)(fa)并且向Hive社區提交了(le)向hive集成Parquet的patch(HIVE-5783),在Hive 0.13版本(ben)之后(hou)正(zheng)式加(jia)入了(le)Parquet的支持;之后(hou)越來越多的查詢引擎(qing)對(dui)此進行支持,也進一步帶動了(le)Parquet的發(fa)(fa)展。
目前Parquet正處(chu)于向2.0版(ban)本邁進的(de)階段,在新的(de)版(ban)本中(zhong)實(shi)現了(le)(le)新的(de)Page存儲格式,針對不同的(de)類(lei)型(xing)優化(hua)編碼(ma)算法(fa),另外豐(feng)富了(le)(le)支持的(de)原(yuan)始類(lei)型(xing),增加了(le)(le)Decimal、Timestamp等類(lei)型(xing)的(de)支持,增加更加豐(feng)富的(de)統計信(xin)息,例(li)如Bloon Filter,能夠(gou)盡可能得將謂(wei)詞下推在元數據層完(wan)成。
總結
本文(wen)(wen)介紹了一(yi)種支(zhi)持(chi)嵌套數據(ju)(ju)模(mo)型對的(de)列式存(cun)儲(chu)系統Parquet,作為大數據(ju)(ju)系統中OLAP查詢(xun)的(de)優(you)化方案(an),它已(yi)經被(bei)多種查詢(xun)引擎原生支(zhi)持(chi),并且(qie)部(bu)分高性能引擎將其作為默認的(de)文(wen)(wen)件存(cun)儲(chu)格式。通(tong)過數據(ju)(ju)編碼和(he)壓縮,以及映射下推和(he)謂詞下推功能,Parquet的(de)性能也較之其它文(wen)(wen)件格式有所提升,可以預見,隨著數據(ju)(ju)模(mo)型的(de)豐富和(he)Ad hoc查詢(xun)的(de)需求,Parquet將會被(bei)更廣泛的(de)使用。
參考
