官網(wǎng)地址:https://parquet.apache.org/docs
編碼:https://www.waitingforcode.com/apache-parquet/encodings-apache-parquet/read
Nested類型編碼參考文章:Dremel: interactive analysis of web-scale datasets
Nested類型編碼參考解釋:https://github.com/julienledem/redelm/wiki/The-striping-and-assembly-algorithms-from-the-Dremel-paper
1. Overview
Parquet是Hadoop生態(tài)里面一個(gè)比較流行的列存儲(chǔ)格式。它存在的意義就是最大化利用壓縮的列存儲(chǔ)表示霎终。
當(dāng)然支持各種壓縮和編碼模式:
- 可以細(xì)化到列級(jí)別堡掏,即每一列用不同算法壓縮暇赤;
- 甚至支持未來的蛾娶,尚未發(fā)明的壓縮技術(shù)盗冷。
注意荔烧,總的來說兑徘,Parquet是一個(gè)非常底層的文件存儲(chǔ)技術(shù)刚盈,它和任何數(shù)據(jù)處理技術(shù)、軟件都是正交的挂脑,這些技術(shù)也都可以用Parquet來存文件藕漱。
Parquet is built from the ground up with complex nested data structures in mind, and uses the [record shredding and assembly algorithm] described in the Dremel paper. We believe this approach is superior to simple flattening of nested name spaces
2. Concepts
- 塊:就是hdfs block
- 文件:就是hdfs文件,元數(shù)據(jù)存于master中崭闲。
- 行組(Row group):邏輯上數(shù)據(jù)表中的一組行肋联。由于實(shí)際上都是列存儲(chǔ),所以在hadoop上找不到所謂的行組刁俭,這只是一個(gè)邏輯上的概念橄仍。
- 列切片(column chunk):某一列中連續(xù)的一片數(shù)據(jù)。其實(shí)是行組的一部分牍戚,但是它是物理存在的侮繁,在文件中連續(xù)存儲(chǔ)的。
- 頁(page):頁時(shí)列切片的一部分如孝。概念上是說不可分的一個(gè)單元宪哩,是針對(duì)于壓縮編碼來說的。一個(gè)列切片里面可能有幾個(gè)不同類型的頁分別壓縮第晰。
總結(jié)一下這個(gè)概念锁孟,一個(gè)文件可能邏輯上包含若干行組彬祖,每個(gè)行組中,每個(gè)列構(gòu)成一個(gè)列切片品抽,列切片內(nèi)由多個(gè)頁構(gòu)成涧至,頁是最小壓縮單元。
編者繪制了下圖幫助概念理解桑包。
在并行化級(jí)別方面:
- MapReduce是并行文件,也可能是并行行組纺非;
- 系統(tǒng)IO其實(shí)是在并行列切片哑了;
- 壓縮針對(duì)的則是頁。
3. File Format
一個(gè)文件用Parquet來存烧颖,基本格式如下圖:
- 首尾的魔數(shù)是系統(tǒng)相關(guān)的弱左,不細(xì)說;
- 文件主體是M個(gè)行組炕淮,N個(gè)列的存儲(chǔ)拆火;
- 比如最開始存第一個(gè)行組,把第一個(gè)行組的N個(gè)列切片涂圆,全部存好们镜,然后進(jìn)入到第二個(gè)行組;
- 文件的最后是整個(gè)文件的元數(shù)據(jù)润歉,包含各個(gè)行組的位置模狭,各個(gè)列切片的位置和一些其它統(tǒng)計(jì)信息。
- 整個(gè)文件的元數(shù)據(jù)放在了文件最后踩衩,這是為了寫入時(shí)能順序?qū)懸槐榫屯旯ぁ?/li>
- 具體存放的時(shí)候嚼鹉,尾部的元數(shù)據(jù)可以單獨(dú)存一個(gè)文件,數(shù)據(jù)文件可以分成幾個(gè)Parquet files來存放驱富;每個(gè)Parquet file可以包含一個(gè)或幾個(gè)行組锚赤。
- 數(shù)據(jù)文件中每個(gè)行組中的每個(gè)頁會(huì)有一個(gè)頁元數(shù)據(jù),頁有三種褐鸥,數(shù)據(jù)頁线脚,字典頁,索引頁晶疼。字典頁每個(gè)列切片最多一個(gè)酒贬,是該列值的編碼字典(編碼部分會(huì)講到);索引頁是一個(gè)高級(jí)功能翠霍。
- 讀取的時(shí)候锭吨,首先讀取元數(shù)據(jù),找到自己想要列切片的位置寒匙,然后順序訪問即可零如。
-
Parquet可以保證躏将,對(duì)于每個(gè)RowGroup只會(huì)被一個(gè)mapper處理。
image.png
3.1 Configurations
對(duì)于Parquet考蕾,有兩個(gè)重要的參數(shù)配置:
- Row Group Size:行組大小
- 行組比較大祸憋,順序掃描時(shí)就可以有更多的順序磁盤訪問;
- 行組過大肖卧,寫入時(shí)需要的內(nèi)存buffer也會(huì)比較大蚯窥。
- 綜合來說,還是會(huì)選擇比較大塞帐,推薦1GB大小拦赠。
- 由于一整個(gè)行組可能需要讀出來,所以最好是一個(gè)Row Group塞進(jìn)一個(gè)HDFS block里面葵姥,所以block也推薦1GB大小荷鼠。
- Page Size:頁大小
- 頁大的話,壓縮率高榔幸,訪問也略快允乐;
- 頁小的話,對(duì)于細(xì)粒度的查詢就可以訪問更少的數(shù)據(jù)削咆;
- 綜合一下牍疏,推薦8KB。
3.2 Metadata
元數(shù)據(jù)有三個(gè)級(jí)別拨齐,文件元數(shù)據(jù)麸澜,列(chunk)元數(shù)據(jù)职员,頁元數(shù)據(jù)灰嫉。
3.3 Types
類型支持的原則是盡量少奇适,因?yàn)镻arquet一般不直接面向用戶号胚,這里只關(guān)注類型如何在磁盤中存儲(chǔ)永高。更復(fù)雜的類型椒舵,比如String春畔,可以用BYTE_ARRAY進(jìn)行支持狈邑,再額外加一個(gè)注解表名如何解釋這個(gè)BYTE_ARRAY即可蹂匹。這樣只用實(shí)現(xiàn)很少量幾種類型的代碼碘菜,就可以表示多種用戶類型。
3.4 Nested Encoding
這里是說層次化類型的編碼限寞。舉個(gè)例子:
如下圖:想json,xml等類型一樣忍啸,數(shù)據(jù)文件中的列也可能是嵌套復(fù)合類型。這些類型不僅僅是列表履植,map這么簡單计雌,而是組合類型的不斷組合,理論上有無限深度的玫霎。
- 在下圖這個(gè)例子里凿滤,repeated其實(shí)就相當(dāng)于列表(單一類型)妈橄,group就相當(dāng)于一個(gè)字典類型(key是固定的,類似C里面的Struct)翁脆。
- 具體到存儲(chǔ)上眷蚓,共有6個(gè)列:
- DocId;
- Links.Backward;
- Links.Forwad;
- Name.Language.Code;
- Name.Language.Country;
- Name.Url;
要處理這樣類型的列,就需要先展平反番,編碼沙热,用的時(shí)候子再解碼恢復(fù)結(jié)構(gòu)查詢。
下面三個(gè)小節(jié)罢缸,分別解決這個(gè)過程中的三個(gè)問題:
- 層次化記錄的無損表示校读;
- 列的快速編碼;
- 高效解碼重組祖能。
3.4.1 Repetition and De?nition Levels
在一個(gè)列式存儲(chǔ)文件中,只給定某兩個(gè)值蛾洛,我們是沒法確定它們是來自一條record還是兩條記錄的养铸,因?yàn)榇嬖谥貜?fù)類型的這個(gè)概念(如上圖Name列,其實(shí)可以理解為列表類型)轧膘。為了得以區(qū)分钞螟,定義了重復(fù)級(jí)別和定義級(jí)別兩個(gè)概念。
- 舉一個(gè)例子谎碍,對(duì)于Name.Language.Code鳞滨,存下來其實(shí)就是連續(xù)的一組字符串了,我們需要知道每一個(gè)Code蟆淀,它屬于哪一個(gè)Language (1個(gè)Name有多個(gè)Language)拯啦,屬于哪一個(gè)Name (1條記錄可能有多個(gè)Name)。
3.4.1.1 repetition levels
重復(fù)級(jí)別 (r) 是說當(dāng)前這個(gè)值熔任,是屬于一個(gè)新紀(jì)錄(r=0)褒链?或者在同一個(gè)記錄的第幾層 (r=n)?
-
舉個(gè)例子疑苔,比如剛才Name.Language.Code這一列甫匹,路徑上有Name, Language兩個(gè)可重復(fù)對(duì)象(列表),所以r的值在【0,2】惦费。- - 以r1為例兵迅,en-us這個(gè)值開啟一個(gè)新對(duì)象,它的r是0,薪贫;en是在Name.Language這個(gè)級(jí)別的重復(fù)(開啟了一個(gè)新的Name.Language)恍箭,r值就是2; en-gb是在Name這個(gè)級(jí)別的重復(fù)(開啟了一個(gè)新的Name),r值是1瞧省。
image.png 重復(fù)級(jí)別解決的是可重復(fù)類型的問題季惯,如果列的路徑上根本沒有可重復(fù)類型(repeated)吠各,那就不需要定義重復(fù)級(jí)別。
仍然有一個(gè)問題是勉抓,雖然我們定義清楚了級(jí)別贾漏,但是沒定義清楚位置。比如剛才的en-gb它到底屬于第二個(gè)name還是第三個(gè)name無法推測(cè)藕筋,因此要在en-gb之前加一個(gè)null纵散。
3.4.1.2 de?nition levels
定義級(jí)別是給所有null值的一個(gè)屬性,把位置定義清楚隐圾。定義級(jí)別d描述的是null是在哪一級(jí)別的缺省伍掀。
-
舉Name.Language.Country為例子:
- 第一個(gè)null, d=2, 描述的是在一個(gè)新的Name.Language中沒有country
- 第二個(gè)null, d=1,描述的是在一個(gè)新的Name中沒有Language暇藏,自然也沒有country
- 第三個(gè)null蜜笤,d=1,也是一個(gè)新的Name中沒有Language,即r2盐碱。
對(duì)于非null值把兔,就賦予正常的嵌套深度。
定義級(jí)別解決的是可選類型的問題瓮顽,如果一列的路徑上所有類型都是必須的(required)县好,那就沒必要定義定義級(jí)別。
3.4.1.3 encoding
最終的編碼是非常緊湊的暖混。每個(gè)列由多個(gè)block組成缕贡,每個(gè)block有兩種levels,有具體的數(shù)值拣播。這些levels也不是全都按序存儲(chǔ)晾咪,主要是按需存儲(chǔ),bit能省則省贮配,一些隱含關(guān)系都被挖掘出來禀酱。
3.4.2 Splitting Records into Columns
本節(jié)聚焦于如何將原始數(shù)據(jù)都覆成列格式:
3.4.3 Record Assembly
通過一個(gè)有限自動(dòng)機(jī)把需要的數(shù)據(jù)組合起來。
【編者:我自己也沒看進(jìn)去這部分內(nèi)容牧嫉,有興趣的朋友可以參考論文和代碼再研究下剂跟。】
3.5 Data Pages
數(shù)據(jù)頁中酣藻,包含定義級(jí)別曹洽,重復(fù)級(jí)別,和編碼后的數(shù)據(jù)值辽剧。如果數(shù)據(jù)全都是Required送淆,并且不嵌套,那就不需要這兩個(gè)級(jí)別的信息怕轿,而只有編碼后的數(shù)據(jù)值偷崩。
3.5.1 Encoding
3.5.1.1 Plain
支持所有類型辟拷,最簡單的存儲(chǔ)方式。
- BOOLEAN: Bit Packed, LSB first
- INT32: 4 bytes little endian
- INT64: 8 bytes little endian
- INT96: 12 bytes little endian (deprecated)
- FLOAT: 4 bytes IEEE little endian
- DOUBLE: 8 bytes IEEE little endian
- BYTE_ARRAY: length in 4 bytes little endian followed by the bytes contained in the array
- FIXED_LEN_BYTE_ARRAY: the bytes contained in the array
3.5.1.2 Dictionary Encoding
- 把所有值建字典阐斜,key是值衫冻,value是id。
- 之前我們說到數(shù)據(jù)頁可能會(huì)配備一個(gè)字典頁谒出,存的就是這個(gè)字典隅俘;
- 這樣數(shù)據(jù)頁中的每一項(xiàng)數(shù)據(jù),就可以用一個(gè)整數(shù)id來存儲(chǔ)笤喳,最大位寬看基數(shù)而定为居。
- 具體來說,數(shù)據(jù)頁頭部第一個(gè)字節(jié)描述每個(gè)值用多寬的bit來存杀狡。后面的各個(gè)值就是這些id蒙畴。一般這些id也會(huì)進(jìn)一步用RLE編碼。
- 當(dāng)字典條目太多呜象,或者字典太大膳凝,會(huì)自動(dòng)退回plain。
3.5.1.3 Run Length Encoding / Bit-Packing Hybrid
-
RLE的思路下圖即可解釋:重復(fù)次數(shù)+重復(fù)值董朝。
image.png - bit-packed基本意思是,一個(gè)int要32bit干跛,但很多時(shí)候用不上32bit子姜,可能10bit就夠了(對(duì)于512以內(nèi)的數(shù)據(jù)),那么就可以縮短寬度存儲(chǔ)楼入。
-
這二者混合使用哥捕,取決于值字符的重復(fù)程度選擇其中一個(gè),可以進(jìn)一步壓縮數(shù)據(jù)頁的這些值嘉熊。
image.png
3.5.1.4 Bit-packed (Deprecated)
廢棄的bit-packed非常簡單遥赚,就是每個(gè)類型固定寬度,然后按順序存儲(chǔ)就可以了阐肤。但注意是bit級(jí)別的凫佛,粒度是很細(xì)的。因?yàn)樗阅芤话阍邢В耆蝗鏡LE或者和RLE混合愧薛,目前只是為了兼容性而存在。
用于:
- 對(duì)定義級(jí)別和重復(fù)級(jí)別的編碼衫画。
3.5.1.5 Delta Encoding
支持INT32, INT64毫炉,和字節(jié)數(shù)組類型。
- 其基本思想是削罩,數(shù)據(jù)的差異一般可能很小瞄勾,如果只存差異的話费奸,就不需要那么長的bit。這尤其適用于時(shí)間进陡、日期愿阐、有公共前綴的字符串。
-
數(shù)據(jù)是分塊的四濒,一旦有過大差異换况,可以通過調(diào)整分塊來改善。每個(gè)分塊都有自己的數(shù)據(jù)寬度盗蟆。
image.png
3.5.1.6 Delta-length byte array
專門用于字節(jié)數(shù)組戈二。
- 數(shù)組長度單獨(dú)提出來用Delta來壓縮;
-
后面跟著純字節(jié)喳资,也利于后續(xù)壓縮算法觉吭。
image.png
3.5.1.7 Byte Stream Split
用于浮點(diǎn)數(shù)。
把每一位單獨(dú)抽出來仆邓,雖然總體大小沒有減少鲜滩,但是利于后續(xù)壓縮算法壓縮。