欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入Parquet文件格式設(shè)計(jì)原理及實(shí)現(xiàn)細(xì)節(jié)

 更新時(shí)間:2023年08月30日 09:17:59   作者:Ye?Ding  
這篇文章主要介紹了深入Parquet文件格式設(shè)計(jì)原理及實(shí)現(xiàn)細(xì)節(jié),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

思考半天決定講一個(gè)大家既熟悉又陌生的話題:Parquet文件。相信每個(gè)做大數(shù)據(jù)的工程師肯定都接觸過Parquet文件,都知道它是一種列式存儲(chǔ)格式,在面對(duì)OLAP查詢時(shí)可以減少讀取的數(shù)據(jù)量,提高查詢性能。但是對(duì)于它的格式具體是如何設(shè)計(jì)的,以及更重要的:為什么這樣設(shè)計(jì),可能就沒有那么清楚了。

這篇文章會(huì)帶你深入Parquet文件的原理和實(shí)現(xiàn)細(xì)節(jié),并試圖說明這些設(shè)計(jì)背后的意義。

Parquet解決什么問題

要理解一個(gè)系統(tǒng),首先第一個(gè)要提出的問題就是

這個(gè)系統(tǒng)為了解決什么問題?

也就是“這個(gè)系統(tǒng)提供了什么功能”。這是理解任何一個(gè)系統(tǒng)都需要關(guān)注的主線。只要心中有這條主線,就不會(huì)陷入各種細(xì)節(jié)的泥沼,而迷失了方向。

對(duì)于Parquet文件來說,這條主線在Twitter宣布Parquet開源的文章中就講得非常清楚

Parquet is an open-source columnar storage format for Hadoop.
...
Not all data people store in Hadoop is a simple table — complex nested structures abound. For example, one of Twitter’s common internal datasets has a schema nested seven levels deep, with over 80 leaf nodes.

也就是說,Twitter想在Hadoop上面設(shè)計(jì)一種新的列式存儲(chǔ)格式,這種格式可以保存包含嵌套結(jié)構(gòu)的數(shù)據(jù)。所以Parquet文件格式試圖解決的問題,用一句話來說,就是列式存儲(chǔ)一個(gè)類型包含嵌套結(jié)構(gòu)的數(shù)據(jù)集。

什么是“包含嵌套結(jié)構(gòu)的數(shù)據(jù)集”呢?舉個(gè)例子

假設(shè)我們要存儲(chǔ)1000個(gè)用戶的電話簿信息,其中每個(gè)用戶的電話簿用下面的這個(gè)結(jié)構(gòu)來表示

{
  "owner": "Lei Li",
  "ownerPhoneNumbers": ["13354127165", "18819972777"],
  "contacts": [
    {
      "name": "Meimei Han",
      "phoneNumber": "18561628306"
    },
    {
      "name": "Lucy",
      "phoneNumber": "14550091758"
    }
  ]
}

可以看到其中 ownerPhoneNumbers 字段是一個(gè)數(shù)組,而contacts字段更是一個(gè)對(duì)象的數(shù)組。所以這個(gè)類型就不能用簡(jiǎn)單的二維表來存儲(chǔ),因?yàn)樗饲短捉Y(jié)構(gòu)。

如果把這個(gè)包含嵌套結(jié)構(gòu)的類型稱為AddressBook,那么Parquet文件的目標(biāo)就是以面向列的方式保存AddressBook對(duì)象所構(gòu)成的數(shù)據(jù)集。

接下來再來講如何“以面向列的方式保存”。對(duì)于一個(gè)二維表的數(shù)據(jù),相信大家可以很容易地想象出怎樣列式地存儲(chǔ)這些數(shù)據(jù),例如

nameagephoneNumber
Lei Li1613354127165
Meimei Han1418561628306
Lucy1514550091758

把它列式存儲(chǔ)就變成了

"Lei Li"
"Meimei Han"
"Lucy"
16
14
15
"13354127165"
"18561628306"
"14550091758"

但如果數(shù)據(jù)是一個(gè)包含數(shù)組和對(duì)象的復(fù)雜嵌套結(jié)構(gòu)呢?可能就不是這么直觀了。

在Parquet里面,保存嵌套結(jié)構(gòu)的方式是把所有字段打平以后順序存儲(chǔ)。

什么意思呢?以電話簿的例子來說,真正有數(shù)據(jù)的其實(shí)只有4列:

  • owner
  • ownerPhoneNumbers
  • contacts.name
  • contacts.phoneNumber

所以只需要把原始數(shù)據(jù)看做是一個(gè)4列的表即可。舉個(gè)例子:

假設(shè)有2條AddressBook記錄

{
  "owner": "Lei Li",
  "ownerPhoneNumbers": ["13354127165", "18819972777"],
  "contacts": [
    {
      "name": "Meimei Han",
      "phoneNumber": "18561628306"
    },
    {
      "name": "Lucy",
      "phoneNumber": "14550091758"
    }
  ]
},
{
  "owner": "Meimei Han",
  "ownerPhoneNumbers": ["15130245254"],
  "contacts": [
    {
      "name": "Lily"
    },
    {
      "name": "Lucy",
      "phoneNumber": "14550091758"
    }
  ]
}

以列式保存之后,就會(huì)變成這樣

"Lei Li"
"Meimei Han"
"13354127165"
"18819972777"
"15130245254"
"Meimei Han"
"Lucy"
"Lily"
"Lucy"
"18561628306"
"14550091758"
"14550091758"

聰明的朋友肯定很快就發(fā)現(xiàn)了,因?yàn)樵冀Y(jié)構(gòu)里有個(gè)數(shù)組,長(zhǎng)度是不定的,如果只是把數(shù)據(jù)按順序存放,那就無法區(qū)分record之間的邊界,也就不知道每個(gè)值究竟屬于哪條record了。所以簡(jiǎn)單地打平是不可行的。

為了解決這個(gè)問題,Parquet的設(shè)計(jì)者引入了兩個(gè)新的概念:repetition level和definition level。這兩個(gè)值會(huì)保存額外的信息,可以用來重構(gòu)出數(shù)據(jù)原本的結(jié)構(gòu)。

關(guān)于repetition level和definition level具體是如何工作的,我會(huì)放到最后來講。這里只需要記住,Parquet文件對(duì)每個(gè)value,都同時(shí)保存了它們的repetition level和definition level,以便確定這個(gè)value屬于哪條record。

Parquet具體是怎么存放數(shù)據(jù)

接下來我們會(huì)深入Parquet文件的內(nèi)部,講講Parquet具體是怎么存放數(shù)據(jù)的。

首先放一張Parquet文件的整體結(jié)構(gòu)圖

其實(shí)Parquet還有一張更常見的結(jié)構(gòu)圖,官方也經(jīng)常引用,但我覺得層次不清晰,反而更讓人費(fèi)解,所以就自己畫了上面這張圖。

看過Parquet的整體結(jié)構(gòu)圖之后,可能你已經(jīng)被這些概念搞迷糊了:Header,Row Group,Column Chunk,Page,F(xiàn)ooter……沒關(guān)系,還是回到我們的主線——列式存儲(chǔ)一個(gè)包含嵌套結(jié)構(gòu)的數(shù)據(jù)集,我會(huì)把解決這個(gè)問題的思路自上而下地拆解,自然而然地就能產(chǎn)生這些概念。

Row Group

首先,因?yàn)槲覀円鎯?chǔ)的對(duì)象是一個(gè)數(shù)據(jù)集,而這個(gè)數(shù)據(jù)集往往包含上億條record,所以我們會(huì)進(jìn)行一次水平切分,把這些record切成多個(gè)“分片”,每個(gè)分片被稱為Row Group。為什么要進(jìn)行水平切分?雖然Parquet的官方文檔沒有解釋,但我認(rèn)為主要和HDFS有關(guān)。因?yàn)镠DFS存儲(chǔ)數(shù)據(jù)的單位是Block,默認(rèn)為128m。如果不對(duì)數(shù)據(jù)進(jìn)行水平切分,只要數(shù)據(jù)量足夠大(超過128m),一條record的數(shù)據(jù)就會(huì)跨越多個(gè)Block,會(huì)增加很多IO開銷。Parquet的官方文檔也建議,把HDFS的block size設(shè)置為1g,同時(shí)把Parquet的parquet.block.size也設(shè)置為1g,目的就是使一個(gè)Row Group正好存放在一個(gè)HDFS Block里面。

Column Chunk

在水平切分之后,就輪到列式存儲(chǔ)標(biāo)志性的垂直切分了。切分方式和上文提到的一致,會(huì)把一個(gè)嵌套結(jié)構(gòu)打平以后拆分成多列,其中每一列的數(shù)據(jù)所構(gòu)成的分片就被稱為Column Chunk。最后再把這些Column Chunk順序地保存。

Page

把數(shù)據(jù)拆解到Column Chunk級(jí)別之后,其結(jié)構(gòu)已經(jīng)相當(dāng)簡(jiǎn)單了。對(duì)Column Chunk,Parquet會(huì)進(jìn)行最后一次水平切分,分解成為一個(gè)個(gè)的Page。每個(gè)Page的默認(rèn)大小為1m。這次的水平切分又是為了什么?盡管Parquet的官方文檔又一次地沒有解釋,我認(rèn)為主要是為了讓數(shù)據(jù)讀取的粒度足夠小,便于單條數(shù)據(jù)或小批量數(shù)據(jù)的查詢。因?yàn)镻age是Parquet文件最小的讀取單位,同時(shí)也是壓縮的單位,如果沒有Page這一級(jí)別,壓縮就只能對(duì)整個(gè)Column Chunk進(jìn)行壓縮,而Column Chunk如果整個(gè)被壓縮,就無法從中間讀取數(shù)據(jù),只能把Column Chunk整個(gè)讀出來之后解壓,才能讀到其中的數(shù)據(jù)。

Header, Index和Footer

最后聊聊Data以外的Metadata部分,主要是:Header,Index和Footer。

Header

Header的內(nèi)容很少,只有4個(gè)字節(jié),本質(zhì)是一個(gè)magic number,用來指示文件類型。這個(gè)magic number目前有兩種變體,分別是“PAR1”和“PARE”。其中“PAR1”代表的是普通的Parquet文件,“PARE”代表的是加密過的Parquet文件。

Index

Index是Parquet文件的索引塊,主要為了支持“謂詞下推”(Predicate Pushdown)功能。謂詞下推是一種優(yōu)化查詢性能的技術(shù),簡(jiǎn)單地來說就是把查詢條件發(fā)給存儲(chǔ)層,讓存儲(chǔ)層可以做初步的過濾,把肯定不滿足查詢條件的數(shù)據(jù)排除掉,從而減少數(shù)據(jù)的讀取和傳輸量。舉個(gè)例子,對(duì)于csv文件,因?yàn)椴恢С种^詞下推,Spark只能把整個(gè)文件的數(shù)據(jù)全部讀出來以后,再用where條件對(duì)數(shù)據(jù)進(jìn)行過濾。而如果是Parquet文件,因?yàn)樽詭ax-Min索引,Spark就可以根據(jù)每個(gè)Page的max和min值,選擇是否要跳過這個(gè)Page,不用讀取這部分?jǐn)?shù)據(jù),也就減少了IO的開銷。

目前Parquet的索引有兩種,一種是Max-Min統(tǒng)計(jì)信息,一種是BloomFilter。其中Max-Min索引是對(duì)每個(gè)Page都記錄它所含數(shù)據(jù)的最大值和最小值,這樣某個(gè)Page是否不滿足查詢條件就可以通過這個(gè)Page的max和min值來判斷。BloomFilter索引則是對(duì)Max-Min索引的補(bǔ)充,針對(duì)value比較稀疏,max-min范圍比較大的列,用Max-Min索引的效果就不太好,BloomFilter可以克服這一點(diǎn),同時(shí)也可以用于單條數(shù)據(jù)的查詢。

Footer

Footer是Parquet元數(shù)據(jù)的大本營,包含了諸如schema,Block的offset和size,Column Chunk的offset和size等所有重要的元數(shù)據(jù)。另外Footer還承擔(dān)了整個(gè)文件入口的職責(zé),讀取Parquet文件的第一步就是讀取Footer信息,轉(zhuǎn)換成元數(shù)據(jù)之后,再根據(jù)這些元數(shù)據(jù)跳轉(zhuǎn)到對(duì)應(yīng)的block和column,讀取真正所要的數(shù)據(jù)。

關(guān)于Footer還有一個(gè)問題,就是為什么Parquet要把元數(shù)據(jù)放在文件的末尾而不是開頭?這主要是為了讓文件寫入的操作可以在一趟(one pass)內(nèi)完成。因?yàn)楹芏嘣獢?shù)據(jù)的信息需要把文件基本寫完以后才知道(例如總行數(shù),各個(gè)Block的offset等),如果要寫在文件開頭,就必須seek回文件的初始位置,大部分文件系統(tǒng)并不支持這種寫入操作(例如HDFS)。而如果寫在文件末尾,那么整個(gè)寫入過程就不需要任何回退。

Parquet如何把嵌套結(jié)構(gòu)編碼進(jìn)列式存儲(chǔ)

講完了Parquet的整體結(jié)構(gòu)之后,我們還剩下最后一個(gè)問題,也就是我之前埋下的伏筆:Parquet如何把嵌套結(jié)構(gòu)編碼進(jìn)列式存儲(chǔ)。在上文中我提到了Parquet是通過repetition level和definition level來解決這個(gè)問題,接下來就會(huì)詳細(xì)地講解一下這是怎么實(shí)現(xiàn)的。

還是上文用到的例子

{
  "owner": "Lei Li",
  "ownerPhoneNumbers": ["13354127165", "18819972777"],
  "contacts": [
    {
      "name": "Meimei Han",
      "phoneNumber": "18561628306"
    },
    {
      "name": "Lucy",
      "phoneNumber": "14550091758"
    }
  ]
},
{
  "owner": "Meimei Han",
  "ownerPhoneNumbers": ["15130245254"],
  "contacts": [
    {
      "name": "Lily"
    },
    {
      "name": "Lucy",
      "phoneNumber": "14550091758"
    }
  ]
}

注意其中的第三列contacts.name,它有4個(gè)值”Meimei Han”,“Lucy”,“Lily”,“Lucy”,其中前兩個(gè)屬于前一條record,后兩個(gè)屬于后一條record。Parquet是如何表達(dá)這個(gè)信息的呢?它是用repetition level這個(gè)值來表達(dá)的。

repetition level主要用來表達(dá)數(shù)組類型字段的長(zhǎng)度,但它并不直接記錄長(zhǎng)度,而是通過記錄嵌套層級(jí)的變化來間接地表達(dá)長(zhǎng)度,即如果嵌套層級(jí)不變,那么說明數(shù)組還在延續(xù),如果嵌套層級(jí)變了,說明前一個(gè)數(shù)組結(jié)束了。如果在某個(gè)值上嵌套層級(jí)由0提高到了1,則這個(gè)值的repetition level就是0。如果在某個(gè)值的位置嵌套層級(jí)不變,則這個(gè)值的repetition level就是它的嵌套層級(jí)。對(duì)于上文中的例子,對(duì)應(yīng)的repetition level就是

ValueRepetition Level
Meimei Han0
Lucy1
Lily0
Lucy1

還不是很明白?換個(gè)更典型的例子

[["a", "b"], ["c", "d", "e"]]

它對(duì)應(yīng)的repetition level會(huì)被編碼成

ValueRepetition Level
a0
b2
c1
d2
e2

因?yàn)檫@個(gè)數(shù)組的嵌套層級(jí)是2,而”a”是從level 0到level 2的邊界,所以它的repetition level是0,”c”是從level 1到level 2的邊界,所以它的repetition level是1,其他字母的嵌套層級(jí)沒有發(fā)生變化,所以它們的repetition level就是2。

總結(jié)一下,repetition level主要用來表達(dá)數(shù)組的長(zhǎng)度。

講完了repetition level,再來講講definition level。與repetition level類似的,definition level主要用來表達(dá)null的位置。因?yàn)镻arquet文件里不會(huì)顯式地存儲(chǔ)null,所以通過definition level來判斷某個(gè)值是否是null。例如對(duì)于下面這個(gè)例子

AddressBook {
    contacts: {
        phoneNumber: "555 987 6543"
    }
    contacts: {
    }
}
AddressBook {
}

對(duì)應(yīng)的definition level是這樣編碼的

ValueDefinition Level
555 987 65432
NULL1
NULL0

可以看到,凡是definition level小于嵌套層級(jí)的,都表達(dá)了這個(gè)值是null。而definition level具體的值則表達(dá)null出現(xiàn)在哪一個(gè)嵌套層級(jí)。

Parquet最難理解的部分到此就結(jié)束了。你或許會(huì)有疑問,如果對(duì)每個(gè)值都保存repetition level和definition level,那么這部分的數(shù)據(jù)量肯定不?。▋蓚€(gè)int32整數(shù),共8個(gè)字節(jié)),搞不好比本來要存的數(shù)據(jù)還要大,是不是本末倒置了?顯然Parquet也考慮到了這個(gè)問題,所以有很多的優(yōu)化措施,例如“對(duì)非數(shù)組類型的值不保存repetition level”,“對(duì)必填字段不保存definition level”等,真正存儲(chǔ)這兩個(gè)level時(shí),也使用的是bit-packing + RLE編碼,盡可能地對(duì)這部分?jǐn)?shù)據(jù)進(jìn)行了壓縮。篇幅有限,就不在這里展開了。

最后聊聊Parquet格式的演進(jìn)

Parquet格式最初由Twitter和Cloudera提出,作為RCFile格式的替代者,和早一個(gè)月提出的ORC格式類似。聯(lián)想到ORC是由Facebook和Hortonworks提出的,這兩者的競(jìng)爭(zhēng)關(guān)系不言自明。(有機(jī)會(huì)可以再來寫寫ORC格式)

Parquet在2013年宣布開源后,2014年被Cloudera捐給Apache基金會(huì),進(jìn)入孵化流程,并于2015年畢業(yè)成為頂級(jí)項(xiàng)目。Parquet的框架在進(jìn)入Apache基金會(huì)之前已經(jīng)基本成型,此后變化得也不快,主要新增了幾個(gè)功能:

  • Column Index
  • BloomFilter
  • 模塊化加密

這些改動(dòng)主要是為了加強(qiáng)對(duì)謂詞下推的支持,但也有個(gè)副作用:文件體積變得更大了。

以上就是深入Parquet文件原理實(shí)現(xiàn)細(xì)節(jié)及設(shè)計(jì)意義的詳細(xì)內(nèi)容,更多關(guān)于Parquet文件原理設(shè)計(jì)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Mybatis的幾種傳參方式詳解

    Mybatis的幾種傳參方式詳解

    這篇文章主要介紹了Mybatis的幾種傳參方式詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-09-09
  • 簡(jiǎn)單了解JAVA SimpleDateFormat yyyy和YYYY的區(qū)別

    簡(jiǎn)單了解JAVA SimpleDateFormat yyyy和YYYY的區(qū)別

    這篇文章主要介紹了簡(jiǎn)單了解JAVA SimpleDateFormat yyyy和YYYY的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • Java通過Freemarker模板實(shí)現(xiàn)生成Word文件

    Java通過Freemarker模板實(shí)現(xiàn)生成Word文件

    FreeMarker是一款模板引擎: 即一種基于模板和要改變的數(shù)據(jù), 并用來生成輸出文本的通用工具。本文將根據(jù)Freemarker模板實(shí)現(xiàn)生成Word文件,需要的可以參考一下
    2022-09-09
  • Java實(shí)戰(zhàn)之圖書管理系統(tǒng)的實(shí)現(xiàn)

    Java實(shí)戰(zhàn)之圖書管理系統(tǒng)的實(shí)現(xiàn)

    這篇文章主要介紹了如何利用Java語言編寫一個(gè)圖書管理系統(tǒng),文中采用的技術(shù)有Springboot、SpringMVC、MyBatis、ThymeLeaf 等,需要的可以參考一下
    2022-03-03
  • Spring Boot啟動(dòng)及退出加載項(xiàng)的方法

    Spring Boot啟動(dòng)及退出加載項(xiàng)的方法

    這篇文章主要介紹了Spring Boot啟動(dòng)及退出加載項(xiàng)的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • Java由淺入深細(xì)數(shù)數(shù)組的操作下

    Java由淺入深細(xì)數(shù)數(shù)組的操作下

    數(shù)組對(duì)于每一門編程語言來說都是重要的數(shù)據(jù)結(jié)構(gòu)之一,當(dāng)然不同語言對(duì)數(shù)組的實(shí)現(xiàn)及處理也不盡相同。Java?語言中提供的數(shù)組是用來存儲(chǔ)固定大小的同類型元素
    2022-04-04
  • Java中反射動(dòng)態(tài)代理接口的詳解及實(shí)例

    Java中反射動(dòng)態(tài)代理接口的詳解及實(shí)例

    這篇文章主要介紹了Java中反射動(dòng)態(tài)代理接口的詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下
    2017-04-04
  • Java基礎(chǔ)教程之List集合的常用方法

    Java基礎(chǔ)教程之List集合的常用方法

    這篇文章主要給大家介紹了關(guān)于Java基礎(chǔ)教程之List集合的常用方法,在Java編程中List集合是一種常用的數(shù)據(jù)結(jié)構(gòu),用于存儲(chǔ)一組元素,有時(shí)候我們需要對(duì)List集合中的元素進(jìn)行分組操作,即將相同屬性或特征的元素歸類到一組,需要的朋友可以參考下
    2023-10-10
  • Maven項(xiàng)目打包成可執(zhí)行Jar文件步驟解析

    Maven項(xiàng)目打包成可執(zhí)行Jar文件步驟解析

    這篇文章主要介紹了Maven項(xiàng)目如何打包成可執(zhí)行Jar文件,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-05-05
  • springboot?sleuth?日志跟蹤問題記錄

    springboot?sleuth?日志跟蹤問題記錄

    Spring?Cloud?Sleuth是一個(gè)在應(yīng)用中實(shí)現(xiàn)日志跟蹤的強(qiáng)有力的工具,使用Sleuth庫可以應(yīng)用于計(jì)劃任務(wù)?、多線程服務(wù)或復(fù)雜的Web請(qǐng)求,尤其是在一個(gè)由多個(gè)服務(wù)組成的系統(tǒng)中,這篇文章主要介紹了springboot?sleuth?日志跟蹤,需要的朋友可以參考下
    2023-07-07

最新評(píng)論