Apache Arrow 簡介

arrow主要focus在幫助 data 序列化, 以便在各種system之間transfer.
arrorw還解決了類型共享計算格式不統一的問題,是高性能計算的基礎.

背景

https://arrow.apache.org/

由於歷史原因,Snowflake一直使用了JSON作爲結果集(ResultSet)的序列化方式,引起了許多問題。首先,JSON的序列化/反序列化的成本實在是太高了:許多cpu cycle都被浪費在了字符串和其他數據類型之間的轉換。
不僅僅是cpu,內存的消耗也是十分巨大的,尤其像是Java這樣的語言,對內存的壓力非常大。其次,使用JSON進行序列化,會導致某些數據類型(浮點數)的精度丟失。

經過一系列的研究,我們最終決定採用Apache Arrow作爲我們新的結果集序列化方式。這篇文章對arrow進行了一些簡單的介紹,並且反思了arrow想解決的一些問題。

Apache Arrow是什麼

  • 數據格式:arrow 定義了一種在內存中表示tabular data的格式。這種格式特別爲數據分析型操作(analytical operation)進行了優化。比如說列式格式(columnar format),能充分利用現代cpu的優勢,進行向量化計算(vectorization)。不僅如此,Arrow還定義了IPC格式,序列化內存中的數據,進行網絡傳輸,或者把數據以文件的方式持久化。
  • 開發庫:arrow定義的格式是與語言無關的,所以任何語言都能實現Arrow定義的格式。arrow項目爲幾乎所有的主流編程語言提供了SDK

說到這裏,大家大概都明白了arrow其實和protobuf很像,只不過protobuf是爲了structured data提供內存表示方式和序列化方案。可是兩者的使用場景卻很不一樣。protobuf主要是序列化structured data,有很多的鍵值對和非常深的nested structure。arrow序列化的對象主要還是表格狀數據。

What is Arrow?

Format

Apache Arrow defines a language-independent columnar memory format for flat and hierarchical data, organized for efficient analytic operations on modern hardware like CPUs and GPUs. The Arrow memory format also supports zero-copy reads for lightning-fast data access without serialization overhead.

Learn more about the design or read the specification.

Libraries

Arrow's libraries implement the format and provide building blocks for a range of use cases, including high performance analytics. Many popular projects use Arrow to ship columnar data efficiently or as the basis for analytic engines.

Libraries are available for C, C++, C#, Go, Java, JavaScript, Julia, MATLAB, Python, R, Ruby, and Rust. See how to install and get started.

Ecosystem

Apache Arrow is software created by and for the developer community. We are dedicated to open, kind communication and consensus decisionmaking. Our committers come from a range of organizations and backgrounds, and we welcome all to participate with us.

Learn more about how you can ask questions and get involved in the Arrow project.

內存表示: Arrow Columnar Format 列式存儲

The columnar format has some key features:

Data adjacency for sequential access (scans)

O(1) (constant-time) random access

SIMD and vectorization-friendly

Relocatable without “pointer swizzling”, allowing for true zero-copy access in shared memory

The Arrow columnar format provides analytical performance and data locality guarantees in exchange for comparatively more expensive mutation operations. This document is concerned only with in-memory data representation and serialization details; issues such as coordinating mutation of data structures are left to be handled by implementations.

arrow在內存中表示數據的最基本單元是array,它代表了一連串長度已知、類型相同的數據。而多個長度相同、類型相同或者不同的array就可以用來表示結果集(或者一部分的結果集)。

舉一個簡單的例子:一個如下圖所示的結果集(或者table)

+------+------+                                 
|   C1 |   C2 |            [
|------+------|              DoubleArray: [ 1.11, 2.22, 3.33],
| 1.11 |  foo |     ->       StringArray: [ foo, bar, NULL]
| 2.22 |  bar |            ]
| 3.33 | NULL |   
+------+------+

就可以表示成一個大小爲2的有序集合,集合中的array(DoubleArray 和 StringArray)長度爲3。arrow限制了array的最大長度,當結果集(或者表)的大小超過了array的最大長度,就需要把結果集水平切分成多個有序集合。

接一下來我們具體來看一下array,arrow是這樣定義一個array的:

  • 邏輯類型(比如 int32 或者 timestamp)
  • 一串buffer(用來存放具體的數據和表示NULL值)
  • array長度
  • array中NULL值的數量
  • dictionary (用於dictionary encoding,比較適用於有很多重複數據的array,相當於一個壓縮算法,不是必需的)

舉一個具體的例子,一個如下的Int32Array:

[1, null, 2, 4, 8]

會被表示成

* Length: 5, Null count: 1
* Validity bitmap buffer:

  |Byte 0 (validity bitmap) | Bytes 1-63            |
  |-------------------------|-----------------------|
  | 00011101                | 0 (padding)           |

* Value Buffer:

  |Bytes 0-3   | Bytes 4-7   | Bytes 8-11  | Bytes 12-15 | Bytes 16-19 | Bytes 20-63 |
  |------------|-------------|-------------|-------------|-------------|-------------|
  | 1          | unspecified | 2           | 4           | 8           | unspecified |

總結一下:

  1. 具體的值存放在value buffer中,相對應的值如果是NULL的話,value buffer中的bytes可以是任意的值。
  2. NULL值是用bitmap來表示,bit 0 表示NULL,bit 1表示非NULL值,value buffer中的值是有意義的。如果array中沒有NULL值,這個bitmap也可以省略。
  3. allocate memory on aligned addresses:每次分配內存的大小總是8或者64的倍數。注意我們僅僅需要一個字節就能表示這個array的NULL值,value buffer也僅僅需要20個字節,但是arrow爲每個buffer都分配64個字節的內存大小。主要原因是便於編譯器生成SIMD指令,進行向量化運算。網上有很多關於向量化運算的文章,有興趣的小夥伴可以自行搜索一下。

Fixed-Size Primitive Type Array (e.g. Int32Array)是最簡單的情況(這裏也沒有考慮dictionary encoding的情況),StringArray 或者其他nested type array 的情況會更加複雜一些,具體的可以參見這裏

序列化與進程間通信(IPC)

之前已經提到了,多個長度相同的array組成的有序集合可以用來表示結果集的子集(或者部分的表),arrow稱這個有序集合爲Record Batch。Record Batch也是序列化的基本單元。arrow定義了一個傳輸協議,能把多個record batch序列化成一個二進制的字節流,並且把這些字節流反序列化成record batch,從讓數據能在不同的進程之間進行交換。

字節流由一連串的message組成,arrow定義了多種message type,主要是schema message和record batch message。一個schema message和多個record batch message就能完整的表示一個結果集(或者一個表)。message的format如下:

<continuation: 0xFFFFFFFF>
<metadata_size: int32>
<metadata_flatbuffer: bytes>
<padding>
<message body>
  • continuation indicator:8個字節,永遠是0xFFFFFFFF,官方文檔稱是爲了解決flatbuffer的alignment要求。
  • metadata_size:8個字節,保存了整個message的metadata序列化之後的字節數。
  • metadata_flatbufffer:metadata序列化之後的字節,arrow使用了flatbuffer對metadata進行了序列化,具體定義可在Message.fbs找到。
  • padding:padding data 使當前數據量是8的倍數
  • message body:schema message沒有,record batch message 有。直接把內存中arrow array 的 value buffer 和 bitmap buffer 寫入這裏。

根據這些message,arrow定義了IPC Streaming Format, 定義如下:

<SCHEMA MESSAGE>
<RECORD BATCH MESSAGE 0>
...
<RECORD BATCH MESSAGE n - 1>
<EOS [optional]: 0xFFFFFFFF 0x00000000>

由於所有record batch都有一樣的schema,所以只需要序列化一個schema message。在反序列化的時候,根據schema message,就能重建所有的record batch。(這裏並沒有討論dictionary encoding的情況)

反思

在傳統的編程世界中,數據只存放與oltp database中(比如說MySQL),application通過JDBC或者ODBC等標準接口和數據庫進行交互。

然而雖然現在的互聯網世界數據的爆炸,數據的使用場景也越來越複雜。arrow適用的場景可能有一下幾個:

  • 同一個系統,多個節點:由於雲計算的普及,數據庫上雲也得到了越來越多的關注。在一個分佈式數據庫的實現中,可能會有許多的query executor節點並行產生結果集。arrow的格式可以讓客戶端並行讀取各個節點產生的結果集。
  • 多個系統可能會同時讀取同一份數據:企業可能會需要data warehouse生成報表,需要spark做一些機器學習。爲了能讓不同的系統之間進行數據的交互,企業經常把數據以文件的形式存放於一些分佈式的文件系統(AWS S3)之上。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章