天天看點

Parquet檔案存儲格式詳細解析

Parquet檔案存儲格式詳細解析
Parquet檔案存儲格式詳細解析

大資料技術與架構

Parquet檔案存儲格式詳細解析
Parquet檔案存儲格式詳細解析

大資料真好玩

Parquet檔案存儲格式詳細解析

猜你想要的:

Hive - ORC 檔案存儲格式詳細解析

一、Parquet的組成

Parquet僅僅是一種存儲格式,它是語言、平台無關的,并且不需要和任何一種資料處理架構綁定,目前能夠和Parquet适配的元件包括下面這些,可以看出基本上通常使用的查詢引擎和計算架構都已适配,并且可以很友善的将其它序列化工具生成的資料轉換成Parquet格式。

查詢引擎: Hive, Impala, Pig, Presto, Drill, Tajo, HAWQ, IBM Big SQL

計算架構: MapReduce, Spark, Cascading, Crunch, Scalding, Kite

資料模型: Avro, Thrift, Protocol Buffers, POJOs

Parquet項目由以下幾個子項目組成:

parquet-format項目由java實作,它定義了所有Parquet中繼資料對象,Parquet的中繼資料是使用Apache Thrift進行序列化并存儲在Parquet檔案的尾部。

parquet-format項目由java實作,它包括多個子產品,包括實作了讀寫Parquet檔案的功能,并且提供一些和其它元件适配的工具,例如Hadoop Input/Output Formats、Hive Serde(目前Hive已經自帶Parquet了)、Pig loaders等。

parquet-compatibility項目,包含不同程式設計語言之間(JAVA和C/C++)讀寫檔案的測試代碼。

parquet-cpp項目,它是用于用于讀寫Parquet檔案的C++庫。

下圖展示了Parquet各個元件的層次以及從上到下互動的方式。

Parquet檔案存儲格式詳細解析

資料存儲層定義了Parquet的檔案格式,其中中繼資料在parquet-format中定義,包括Parquet原始類型定義、Page類型、編碼類型、壓縮類型等等。

對象轉換層完成其他對象模型與Parquet内部資料模型的映射和轉換,Parquet的編碼方式使用的是striping and assembly算法。

對象模型層定義了如何讀取Parquet檔案的内容,這一層轉換包括Avro、Thrift、PB等序列化格式、Hive serde等的适配。并且為了幫助大家了解和使用,Parquet提供了org.apache.parquet.example包實作了java對象和Parquet檔案的轉換。

Parquet支援嵌套的資料模型,類似于Protocol Buffers,每一個資料模型的schema包含多個字段,每一個字段又可以包含多個字段,每一個字段有三個屬性:重複數、資料類型和字段名,重複數可以是以下三種:required(出現1次),repeated(出現0次或多次),optional(出現0次或1次)。每一個字段的資料類型可以分成兩種:group(複雜類型)和primitive(基本類型)。例如Dremel中提供的Document的schema示例,它的定義如下:

可以把這個Schema轉換成樹狀結構,根節點可以了解為repeated類型,如下圖: 

Parquet檔案存儲格式詳細解析

可以看出在Schema中所有的基本類型字段都是葉子節點,在這個Schema中一共存在6個葉子節點,如果把這樣的Schema轉換成扁平式的關系模型,就可以了解為該表包含六個列。Parquet中沒有Map、Array這樣的複雜資料結構,但是可以通過repeated和group組合來實作這樣的需求。在這個包含6個字段的表中有以下幾個字段和每一條記錄中它們可能出現的次數:

由于在一個表中可能存在出現任意多次的列,對于這些列需要标示出現多次或者等于NULL的情況,它是由Striping/Assembly算法實作的。

上文介紹了Parquet的資料模型,在Document中存在多個非required列,由于Parquet一條記錄的資料分散的存儲在不同的列中,如何組合不同的列值組成一條記錄是由Striping/Assembly算法決定的,在該算法中列的每一個值都包含三部分:value、repetition level和definition level。

為了支援repeated類型的節點,在寫入的時候該值等于它和前面的值在哪一層節點是不共享的。在讀取的時候根據該值可以推導出哪一層上需要建立一個新的節點,例如對于這樣的一個schema和兩條記錄。

計算repetition level值的過程如下:

value=a是一條記錄的開始,和前面的值(已經沒有值了)在根節點(第0層)上是不共享的,是以repeated level=0.

value=b它和前面的值共享了level1這個節點,但是level2這個節點上是不共享的,是以repeated level=2.

同理value=c, repeated level=2.

value=d和前面的值共享了根節點(屬于相同記錄),但是在level1這個節點上是不共享的,是以repeated level=1.

value=h和前面的值不屬于同一條記錄,也就是不共享任何節點,是以repeated level=0.

根據以上的分析每一個value需要記錄的repeated level值如下:

Parquet檔案存儲格式詳細解析

在讀取的時候,順序的讀取每一個值,然後根據它的repeated level建立對象,當讀取value=a時repeated level=0,表示需要建立一個新的根節點(新記錄),value=b時repeated level=2,表示需要建立一個新的level2節點,value=d時repeated level=1,表示需要建立一個新的level1節點,當所有列讀取完成之後可以建立一條新的記錄。本例中當讀取檔案建構每條記錄的結果如下:

Parquet檔案存儲格式詳細解析

可以看出repeated level=0表示一條記錄的開始,并且repeated level的值隻是針對路徑上的repeated類型的節點,是以在計算該值的時候可以忽略非repeated類型的節點,在寫入的時候将其了解為該節點和路徑上的哪一個repeated節點是不共享的,讀取的時候将其了解為需要在哪一層建立一個新的repeated節點,這樣的話每一列最大的repeated level值就等于路徑上的repeated節點的個數(不包括根節點)。減小repeated level的好處能夠使得在存儲使用更加緊湊的編碼方式,節省存儲空間。

有了repeated level我們就可以構造出一個記錄了,為什麼還需要definition levels呢?由于repeated和optional類型的存在,可能一條記錄中某一列是沒有值的,假設我們不記錄這樣的值就會導緻本該屬于下一條記錄的值被當做目前記錄的一部分,進而造成資料的錯誤,是以對于這種情況需要一個占位符标示這種情況。

definition level的值僅僅對于空值是有效的,表示在該值的路徑上第幾層開始是未定義的,對于非空的值它是沒有意義的,因為非空值在葉子節點是定義的,所有的父節點也肯定是定義的,是以它總是等于該列最大的definition levels。例如下面的schema。

它包含一個列a.b.c,這個列的的每一個節點都是optional類型的,當c被定義時a和b肯定都是已定義的,當c未定義時我們就需要标示出在從哪一層開始時未定義的,如下面的值:

Parquet檔案存儲格式詳細解析

由于definition level隻需要考慮未定義的值,而對于repeated類型的節點,隻要父節點是已定義的,該節點就必須定義(例如Document中的DocId,每一條記錄都該列都必須有值,同樣對于Language節點,隻要它定義了Code必須有值),是以計算definition level的值時可以忽略路徑上的required節點,這樣可以減小definition level的最大值,優化存儲。

本節我們使用Dremel論文中給的Document示例和給定的兩個值r1和r2展示計算repeated level和definition level的過程,這裡把未定義的值記錄為NULL,使用R表示repeated level,D表示definition level。

Parquet檔案存儲格式詳細解析

首先看DocuId這一列,對于r1,DocId=10,由于它是記錄的開始并且是已定義的,是以R=0,D=0,同樣r2中的DocId=20,R=0,D=0。

對于Links.Forward這一列,在r1中,它是未定義的但是Links是已定義的,并且是該記錄中的第一個值,是以R=0,D=1,在r1中該列有兩個值,value1=10,R=0(記錄中該列的第一個值),D=2(該列的最大definition level)。

對于Name.Url這一列,r1中它有三個值,分别為url1=’http://A‘,它是r1中該列的第一個值并且是定義的,是以R=0,D=2;value2=’http://B‘,和上一個值value1在Name這一層是不相同的,是以R=1,D=2;value3=NULL,和上一個值value2在Name這一層是不相同的,是以R=1,但它是未定義的,而Name這一層是定義的,是以D=1。r2中該列隻有一個值value3=’http://C‘,R=0,D=2.

最後看一下Name.Language.Code這一列,r1中有4個值,value1=’en-us’,它是r1中的第一個值并且是已定義的,是以R=0,D=2(由于Code是required類型,這一列repeated level的最大值等于2);value2=’en’,它和value1在Language這個節點是不共享的,是以R=2,D=2;value3=NULL,它是未定義的,但是它和前一個值在Name這個節點是不共享的,在Name這個節點是已定義的,是以R=1,D=1;value4=’en-gb’,它和前一個值在Name這一層不共享,是以R=1,D=2。在r2中該列有一個值,它是未定義的,但是Name這一層是已定義的,是以R=0,D=1.

Parquet檔案是以二進制方式存儲的,是以是不可以直接讀取的,檔案中包括該檔案的資料和中繼資料,是以Parquet格式檔案是自解析的。在HDFS檔案系統和Parquet檔案中存在如下幾個概念。

HDFS塊(Block):它是HDFS上的最小的副本機關,HDFS會把一個Block存儲在本地的一個檔案并且維護分散在不同的機器上的多個副本,通常情況下一個Block的大小為256M、512M等。

HDFS檔案(File):一個HDFS的檔案,包括資料和中繼資料,資料分散存儲在多個Block中。

行組(Row Group):按照行将資料實體上劃分為多個單元,每一個行組包含一定的行數,在一個HDFS檔案中至少存儲一個行組,Parquet讀寫的時候會将整個行組緩存在記憶體中,是以如果每一個行組的大小是由記憶體大的小決定的,例如記錄占用空間比較小的Schema可以在每一個行組中存儲更多的行。

列塊(Column Chunk):在一個行組中每一列儲存在一個列塊中,行組中的所有列連續的存儲在這個行組檔案中。一個列塊中的值都是相同類型的,不同的列塊可能使用不同的算法進行壓縮。

頁(Page):每一個列塊劃分為多個頁,一個頁是最小的編碼的機關,在同一個列塊的不同頁可能使用不同的編碼方式。

通常情況下,在存儲Parquet資料的時候會按照Block大小設定行組的大小,由于一般情況下每一個Mapper任務處理資料的最小機關是一個Block,這樣可以把每一個行組由一個Mapper任務處理,增大任務執行并行度。Parquet檔案的格式如下圖所示

Parquet檔案存儲格式詳細解析

上圖展示了一個Parquet檔案的内容,一個檔案中可以存儲多個行組,檔案的首位都是該檔案的Magic Code,用于校驗它是否是一個Parquet檔案,Footer length了檔案中繼資料的大小,通過該值和檔案長度可以計算出中繼資料的偏移量,檔案的中繼資料中包括每一個行組的中繼資料資訊和該檔案存儲資料的Schema資訊。除了檔案中每一個行組的中繼資料,每一頁的開始都會存儲該頁的中繼資料,在Parquet中,有三種類型的頁:資料頁、字典頁和索引頁。資料頁用于存儲目前行組中該列的值,字典頁存儲該列值的編碼字典,每一個列塊中最多包含一個字典頁,索引頁用來存儲目前行組下該列的索引,目前Parquet中還不支援索引頁,但是在後面的版本中增加。

在執行MR任務的時候可能存在多個Mapper任務的輸入是同一個Parquet檔案的情況,每一個Mapper通過InputSplit标示處理的檔案範圍,如果多個InputSplit跨越了一個Row Group,Parquet能夠保證一個Row Group隻會被一個Mapper任務處理。

說到列式存儲的優勢,映射下推是最突出的,它意味着在擷取表中原始資料時隻需要掃描查詢中需要的列,由于每一列的所有值都是連續存儲的,是以分區取出每一列的所有值就可以實作TableScan算子,而避免掃描整個表檔案内容。

在Parquet中原生就支援映射下推,執行查詢的時候可以通過Configuration傳遞需要讀取的列的資訊,這些列必須是Schema的子集,映射每次會掃描一個Row Group的資料,然後一次性得将該Row Group裡所有需要的列的Cloumn Chunk都讀取到記憶體中,每次讀取一個Row Group的資料能夠大大降低随機讀的次數,除此之外,Parquet在讀取的時候會考慮列是否連續,如果某些需要的列是存儲位置是連續的,那麼一次讀操作就可以把多個列的資料讀取到記憶體。

在資料庫之類的查詢系統中最常用的優化手段就是謂詞下推了,通過将一些過濾條件盡可能的在最底層執行可以減少每一層互動的資料量,進而提升性能,例如”select count(1) from A Join B on A.id = B.id where A.a > 10 and B.b < 100”SQL查詢中,在處理Join操作之前需要首先對A和B執行TableScan操作,然後再進行Join,再執行過濾,最後計算聚合函數傳回,但是如果把過濾條件A.a > 10和B.b < 100分别移到A表的TableScan和B表的TableScan的時候執行,可以大大降低Join操作的輸入資料。

無論是行式存儲還是列式存儲,都可以在将過濾條件在讀取一條記錄之後執行以判斷該記錄是否需要傳回給調用者,在Parquet做了更進一步的優化,優化的方法時對每一個Row Group的每一個Column Chunk在存儲的時候都計算對應的統計資訊,包括該Column Chunk的最大值、最小值和空值個數。通過這些統計值和該列的過濾條件可以判斷該Row Group是否需要掃描。另外Parquet未來還會增加諸如Bloom Filter和Index等優化資料,更加有效的完成謂詞下推。

在使用Parquet的時候可以通過如下兩種政策提升查詢性能:1、類似于關系資料庫的主鍵,對需要頻繁過濾的列設定為有序的,這樣在導入資料的時候會根據該列的順序存儲資料,這樣可以最大化的利用最大值、最小值實作謂詞下推。2、減小行組大小和頁大小,這樣增加跳過整個行組的可能性,但是此時需要權衡由于壓縮和編碼效率下降帶來的I/O負載。

相比傳統的行式存儲,Hadoop生态圈近年來也湧現出諸如RC、ORC、Parquet的列式存儲格式,它們的性能優勢主要展現在兩個方面:1、更高的壓縮比,由于相同類型的資料更容易針對不同類型的列使用高效的編碼和壓縮方式。2、更小的I/O操作,由于映射下推和謂詞下推的使用,可以減少一大部分不必要的資料掃描,尤其是表結構比較龐大的時候更加明顯,由此也能夠帶來更好的查詢性能

Parquet檔案存儲格式詳細解析

上圖是展示了使用不同格式存儲TPC-H和TPC-DS資料集中兩個表資料的檔案大小對比,可以看出Parquet較之于其他的二進制檔案存儲格式能夠更有效的利用存儲空間,而新版本的Parquet(2.0版本)使用了更加高效的頁存儲方式,進一步的提升存儲空間

Parquet檔案存儲格式詳細解析

上圖展示了Twitter在Impala中使用不同格式檔案執行TPC-DS基準測試的結果,測試結果可以看出Parquet較之于其他的行式存儲格式有較明顯的性能提升。

Parquet檔案存儲格式詳細解析

上圖展示了criteo公司在Hive中使用ORC和Parquet兩種列式存儲格式執行TPC-DS基準測試的結果,測試結果可以看出在資料存儲方面,兩種存儲格式在都是用snappy壓縮的情況下量中存儲格式占用的空間相差并不大,查詢的結果顯示Parquet格式稍好于ORC格式,兩者在功能上也都有優缺點,Parquet原生支援嵌套式資料結構,而ORC對此支援的較差,這種複雜的Schema查詢也相對較差;而Parquet不支援資料的修改和ACID,但是ORC對此提供支援,但是在OLAP環境下很少會對單條資料修改,更多的則是批量導入。

自從2012年由Twitter和Cloudera共同研發Parquet開始,該項目一直處于高速發展之中,并且在項目之初就将其貢獻給開源社群,2013年,Criteo公司加入開發并且向Hive社群送出了向hive內建Parquet的patch(HIVE-5783),在Hive 0.13版本之後正式加入了Parquet的支援;之後越來越多的查詢引擎對此進行支援,也進一步帶動了Parquet的發展。

目前Parquet正處于向2.0版本邁進的階段,在新的版本中實作了新的Page存儲格式,針對不同的類型優化編碼算法,另外豐富了支援的原始類型,增加了Decimal、Timestamp等類型的支援,增加更加豐富的統計資訊,例如Bloon Filter,能夠盡可能得将謂詞下推在中繼資料層完成。

本文介紹了一種支援嵌套資料模型對的列式存儲系統Parquet,作為大資料系統中OLAP查詢的優化方案,它已經被多種查詢引擎原生支援,并且部分高性能引擎将其作為預設的檔案存儲格式。通過資料編碼和壓縮,以及映射下推和謂詞下推功能,Parquet的性能也較之其它檔案格式有所提升,可以預見,随着資料模型的豐富和Ad hoc查詢的需求,Parquet将會被更廣泛的使用。

Dremel: Interactive Analysis of Web-Scale Datasets

Dremel made simple with Parquet

Parquet: Columnar storage for the people

Efficient Data Storage for Analytics with Apache Parquet 2.0

深入分析Parquet列式存儲格式

Apache Parquet Document

http://blog.csdn.net/yu616568/article/details/50993491

http://blog.csdn.net/yu616568/article/details/51188479

Parquet檔案存儲格式詳細解析
Parquet檔案存儲格式詳細解析

版權聲明:

本文為大資料技術與架構整理,原作者獨家授權。未經原作者允許轉載追究侵權責任。

編輯|冷眼丶

微信公衆号|import_bigdata

Parquet檔案存儲格式詳細解析

繼續閱讀