您好,登錄后才能下訂單哦!
這篇文章主要介紹“Apache Arrow是什么”,在日常操作中,相信很多人在Apache Arrow是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Apache Arrow是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
現存的大數據分析系統基本都基于各自不同的內存數據結構,這就會帶來一系列的重復工作:從計算引擎上看,算法必須基于項目特有的數據結構、API 與算法之間出現不必要的耦合;從數據獲取上看,數據加載時必須反序列化,而每一種數據源都需要單獨實現相應的加載器;從生態系統上看,跨項目、跨語言的合作無形之中被阻隔。能否減少或消除數據在不同系統間序列化、反序列化的成本?能否跨項目復用算法及 IO 工具?能否推動更廣義的合作,讓數據分析系統的開發者聯合起來?在這樣的使命驅動下,Arrow 就誕生了。
與其它項目不同,Arrow 項目的草臺班子由 5 個 Apache Members、6 個 PMC Chairs 和一些其它項目的 PMC 及 committer 構成,他們直接找到 ASF 董事會,征得同意后直接以頂級 Apache 項目身份啟動。想了解項目的詳細歷史可以閱讀項目 Chair,Jacques Nadeau 寫的這篇博客。另外,這張 google sheet 記錄著項目的取名過程,取名為 Arrow 的原因是:”math symbol for vector. and arrows are fast. also alphabetically will show up on top.” 可以說考慮得相當全面 ????。
Arrow 項目的愿景是提供內存數據分析 (in-memory analytics) 的開發平臺,讓數據在異構大數據系統間移動、處理地更快:
項目主要由 3 部分構成:
為分析查詢引擎 (analytical query engines)、數據幀 (data frames) 設計的內存列存數據格式
用于 IPC/RPC 的二進制協議
用于構建數據處理應用的開發平臺
整個項目的基石是基于內存的列存數據格式,現在將它的特點羅列如下:
標準化 (standardized),與語言無關 (language-independent)
同時支持平鋪 (flat) 和層級 (hierarchical) 數據結構
硬件感知 (hardware-aware)
詳細、準確的格式定義請閱讀官方文檔,本節內容參考了官方文檔及 Daniel Abadi 的這篇博客。
在實踐中,工程師通常會將系統中的數據通過多個二維數據表建模,每張數據表的一行表示一個實體 (entity),一列表示同一屬性。然而,在硬件中存儲器通常是一維的,即計算機程序只能線性地、沿同一方向地從內存或硬盤中讀取數據,因此存儲二維數據表就有兩種典型方案:行存和列存。通常前者適用于 OLTP 場景,后者適用于 OLAP 場景,Arrow 是面向數據分析開發的,因此采用后者。
任何一張數據表都可能由不類型的數據列構成。以某張用戶表為例,表中可能包含如年齡 (integer)、姓名 (varchar)、出生日期 (date) 等屬性。Arrow 將數據表中所有可能的列數據分成兩類,定長和變長,并基于定長和變長數據類型構建出更復雜的嵌套數據類型。
定長的數據列格式如下所示:
1 2 3 4 5 6 | type FixedColumn struct { data []byte length int nullCount int nullBitmap []byte // bit 0 is null, 1 is not null } |
除了數據數組 (data) 外,還包含:
數組長度 (length)
null 元素的個數 (nullCount)
null 位圖 (nullBitmap)
以 Int32 數組:[1, null, 2, 4, 8]
為例,它的結構如下:
1 2 3 4 5 6 7 8 9 | length: 5, nullCount: 1 nullBitmap: |Byte 0 (null bitmap) | Bytes 1-63 | |---------------------|-----------------------| | 00011101 | 0 (padding) | data: |Bytes 0-3 | Bytes 4-7 | Bytes 8-11 | Bytes 12-15 | Bytes 16-19 | Bytes 20-63 | |------------|-------------|-------------|-------------|-------------|-------------| | 1 | unspecified | 2 | 4 | 8 | unspecified | |
這里有一個值得關注的設計決定,無論數組中的某個元素 (cell) 是否是 null,在定長數據格式中 Arrow 都會讓該元素占據規定長度的空間;另一種備選方案就是不給 null 元素分配任何空間。前者可以利用指針代數支持 O(1) 的隨機訪問,后者在隨機訪問時需要先利用 nullBitmap 計算出位移。如果是順序訪問,后者需要的內存帶寬更小,性能更優,因此這里主要體現的是存儲空間與隨機訪問性能的權衡,Arrow 選擇傾向是后者。
從 nullBitmap 的結構可以看出,Arrow 采用 little-endian 存儲字節數據。
變長的數據列格式如下所示:
1 2 3 4 5 6 7 | type VarColumn struct { data []byte offsets []int64 length int nullCount int nullBitmap []byte // bit 0 is null, 1 is not null } |
可以看出,比定長列僅多存一個偏移量數組 (offsets)。offsets 的第一個元素固定為 0,最后一個元素為數據的長度,即與 length 相等,那么關于第 i 個變長元素:
1 2 | pos := column.offsets[i] // 位置 size := column.offsets[i+1] - column.offsets[i] // 大小 |
另一種備選方案是在 data 中利用特殊的字符分隔不同元素,在個別查詢場景下,后者能取得更優的性能。如掃描字符串列中包含某兩個連續字母的所有列:利用 Arrow 的格式需要頻繁地訪問 offsets 來遍歷 data,但利用特殊分隔符的解決方案直接遍歷一次 data 即可。而在其它場景下,如查詢某字符串列中值和 “hello world” 相等的字符串,這時利用 offsets 能過濾掉所有長度不為 11 的列,因此利用 Arrow 的格式能獲取更優的性能。
數據處理過程中,一些復雜數據類型如 JSON、struct、union 都很受開發者歡迎,我們可以將這些數據類型歸類為嵌套數據類型。Arrow 處理嵌套數據類型的方式很優雅,并未引入定長和變長數據列之外的概念,而是直接利用二者來構建。假設以一所大學的班級 (Class) 信息數據列為例,該列中有以下兩條數據:
1 2 3 4 5 6 7 8 9 10 11 | // 1 Name: Introduction to Database Systems Instructor: Daniel Abadi Students: Alice, Bob, Charlie Year: 2019 // 2 Name: Advanced Topics in Database Systems Instructor: Daniel Abadi Students: Andrew, Beatrice Year: 2020 |
我們可以將改嵌套數據結構分成 4 列:Name、Instructor、Students 以及 Year,其中 Name 和 Instructor 是變長字符串列,Year 是定長整數列,Students 是字符串數組列 (二維數組),它們的存儲結構分別如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | Name Column: data: Introduction to Database SystemsAdvanced Topics in Database Systems offsets: 0, 32, 67 length: 2 nullCount: 0 nullBitmap: | Byte 0 | Bytes 1-63 | |----------|------------| | 00000011 | 0 (padding)| Instructor Column: data: Daniel AbadiDaniel Abadi offsets: 0, 12, 24 length: 2 nullCount: 0 nullBitmap: | Byte 0 | Bytes 1-63 | |----------|------------| | 00000011 | 0 (padding)| Students Column data: AliceBobCharlieAndrewBeatrice students offsets: 0, 5, 8, 15, 21, 29 students length: 5 students nullCount: 0 students nullBitmap: | Byte 0 | Bytes 1-63 | |----------|------------| | 00011111 | 0 (padding)| nested student list offsets: 0, 3, 5 nested student list length: 2 nested student list nullCount: 0 nested student list nullBitmap: | Byte 0 | Bytes 1-63 | |----------|------------| | 00000011 | 0 (padding)| Year Column data: 2019|2019 length: 2 nullCount: 0 nullBitmap: | Byte 0 | Bytes 1-63 | |----------|------------| | 00000011 | 0 (padding)| |
這里的 Students 列本身就是嵌套數據結構,而外層的 Class 表包含了 Students 列,可以看出這種巧思能支持無限嵌套,是很值得稱贊的設計。
Arrow 列存格式的所有實現都需要考慮數據內存地址的對齊 (alignment) 以及填充 (padding),通常推薦將地址按 8 或 64 字節對齊,若不足 8 或 64 字節的整數倍則按需補全。這主要是為了利用現代 CPU 的 SIMD 指令,將計算向量化。
計算機發展的幾十年來,絕大多數數據引擎都采用行存格式,主要原因在于早期的數據應用負載模式基本都逃不出單個實體的增刪改查。面對此類負載,如果采用列存格式存儲數據,讀取一個實體數據就需要在存儲器上來回跳躍,找到該實體的不同屬性,本質上是在執行隨機訪問。但隨著時間的推移,數據的增多,負載變得更加復雜,數據分析的負載模式逐漸顯露,即每次訪問一組實體的少數幾個屬性,然后聚合出分析結果,這時候列存格式的地位便逐漸提高。
在 Hadoop 生態中,Apache Parquet 和 Apache ORC 已經成為最流行的兩種文件存儲格式,它們核心價值也是圍繞著列存數據格式建立,那么我們為什么還需要 Arrow?這里我們可以從兩個角度來看待數據存儲:
存儲格式:行存 (row-wise/row-based)、列存 (column-wise/column-based/columnar)
主要存儲器:面向磁盤 (disk-oriented)、面向內存 (memory-oriented)
盡管三者都采用列存格式,但 Parquet 和 ORC 是面向磁盤設計,而 Arrow 是面向內存設計。為了理解面向磁盤設計與面向內存設計的區別,我們來看 Daniel Abadi 做的一個實驗。
在一臺 Amazon EC2 的 t2.medium 實例上,創建一張包含 60,000,000 行數據的表,每行包含 6 個屬性,每個屬性值都是 int32 類型的數據,因此每行需要 24 字節空間,整張表占用約 1.5GB 空間。我們將這張表分別用行存格式和列存格式保存一份,然后執行一個簡單的查詢:在第一列中查找與特定值相等的數據,即:
1 | SELECT a FROM t WHERE t.a = 477638700; |
不論是行存還是列存版本,CPU 的工作都是獲取整數與目標整數進行比較。但在行存版本中執行該查詢需要掃描每行,即全部 1.5GB 數據,而在列存版本中執行該查詢只需掃描第一列,即 0.25GB 數據,因此后者的執行效率理論上應該是前者的 6 倍。然而,實際的結果如下所示:
列存版本與行存版本的性能竟然相差無幾!原因在于實驗執行時關閉了所有 CPU 優化 (vectorization/SIMD processing),使得該查詢的瓶頸出現在 CPU 處理上。我們來一起分析一下其中的原因:根據經驗,從內存掃描數據到 CPU 中的吞吐能達到 30GB/s,現代 CPU 的處理頻率能達到 3GHz,即每秒 30 億 CPU 指令,因此即便處理器可以在一個 CPU 周期執行 32 位整數比較,它的吞吐最多為 12 GB/s,遠遠小于內存輸送數據的吞吐。因此不論是行存還是列存,從內存中輸送 0.25GB 還是 1.5GB 數據到 CPU 中,都不會對結果有大的影響。
如果打開 CPU 優化選項,情況就大不相同。對于列存數據,只要這些整數在內存中連續存放,編譯器可以將簡單的操作向量化,如 32 位整數的比較。通常,向量化后處理器在單條指令中能夠同時將 4 個 32 位整數與指定值比較。優化后再執行相同的查詢,實驗的結果如下圖所示:
可以看到與預期相符的 4 倍差異。不過值得注意的是,此時 CPU 仍然是瓶頸。如果內存帶寬是瓶頸的話,我們將能夠看到列存版本與行存版本的性能差異達到 6 倍。
從以上實驗可以看出,針對少量屬性的順序掃描查詢的工作負載,列存格式要優于行存格式,這與數據是在磁盤上還是內存中無關,但它們優于行存格式的理由不同。如果以磁盤為主要存儲,CPU 的處理速度要遠遠高于數據從磁盤移動到 CPU 的速度,列存格式的優勢在于能通過更適合的壓縮算法減少磁盤 IO;如果以內存為主要存儲,數據移動速度的影響將變得微不足道,此時列存格式的優勢在于它能夠更好地利用向量化處理。
這個實驗告訴我們:數據存儲格式的設計決定在不同瓶頸下的目的不同。最典型的就是壓縮,對于 disk-oriented 場景,更高的壓縮率幾乎總是個好主意,利用計算資源換取空間可以利用更多的 CPU 資源,減輕磁盤 IO 的壓力;對于 memory-oriented 場景,壓縮只會讓 CPU 更加不堪重負。
現在要對比 Parquet/ORC 與 Arrow 就變得容易一些。因為 Parquet 和 ORC 是為磁盤而設計,支持高壓縮率的壓縮算法,如 snappy、gzip、zlib 等壓縮技術就十分必要。而 Arrow 為內存而設計,對壓縮算法幾乎沒有要求,更傾向于直接存儲原生的二進制數據。面向磁盤與面向內存的另一個不同點在于:盡管磁盤和內存的順序訪問效率都要高于隨機訪問,但在磁盤中,這個差異在 2-3 個數量級,而在內存中通常在 1 個數量級內。因此要均攤一次隨機訪問的成本,需要在磁盤中連續讀取上千條數據,而在內存中僅需要連續讀取十條左右的數據。這種差異意味著 內存場景下的 batch 大小 (如 Arrow 的 64KB) 要小于磁盤場景下的 batch 大小。
到此,關于“Apache Arrow是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。