From ebe1a8a55e79d47626d3d622db80d967626d1269 Mon Sep 17 00:00:00 2001 From: David Li
00000000: 50 47 43 4f 50 59 0a ff PGCOPY.. COPY signature, flags,
+00000008: 0d 0a 00 00 00 00 00 00 ........ and extension
+00000010: 00 00 00 00 03 00 00 00 ........ Values in row
+00000018: 08 00 00 00 00 00 00 00 ........ Length of value
+00000020: 01 00 00 00 03 66 6f 6f .....foo Data
+00000028: 00 00 00 08 00 00 00 00 ........
+00000030: 00 00 00 40 00 03 00 00 ...@....
+00000038: 00 08 00 00 00 00 00 00 ........
+00000040: 00 02 00 00 00 0f 61 20 ......a
+00000048: 6c 6f 6e 67 65 72 20 73 longer s
+00000050: 74 72 69 6e 67 00 00 00 tring...
+00000058: 08 00 00 00 00 00 00 00 ........
+00000060: 80 00 03 00 00 00 08 00 ........
+00000068: 00 00 00 00 00 00 03 00 ........
+00000070: 00 00 12 79 65 74 20 61 ...yet a
+00000078: 6e 6f 74 68 65 72 20 73 nother s
+00000080: 74 72 69 6e 67 00 00 00 tring...
+00000088: 08 00 00 00 00 00 00 00 ........
+00000090: 0a ff ff ... End of stream00000000: ff ff ff ff d8 00 00 00 ........ IPC message length
+00000008: 10 00 00 00 00 00 0a 00 ........ IPC schema
+⋮ (208 bytes)
+000000e0: ff ff ff ff f8 00 00 00 ........ IPC message length
+000000e8: 14 00 00 00 00 00 00 00 ........ IPC record batch
+⋮ (240 bytes)
+000001e0: 01 00 00 00 00 00 00 00 ........ Data for column #1
+000001e8: 02 00 00 00 00 00 00 00 ........
+000001f0: 03 00 00 00 00 00 00 00 ........
+000001f8: 00 00 00 00 03 00 00 00 ........ String offsets
+00000200: 12 00 00 00 24 00 00 00 ....$...
+00000208: 66 6f 6f 61 20 6c 6f 6e fooa lon Data for column #2
+00000210: 67 65 72 20 73 74 72 69 ger stri
+00000218: 6e 67 79 65 74 20 61 6e ngyet an
+00000220: 6f 74 68 65 72 20 73 74 other st
+00000228: 72 69 6e 67 00 00 00 00 ring.... Alignment padding
+00000230: 40 00 00 00 00 00 00 00 @....... Data for column #3
+00000238: 80 00 00 00 00 00 00 00 ........
+00000240: 0a 00 00 00 00 00 00 00 ........
+00000248: ff ff ff ff 00 00 00 00 ........ IPC end-of-stream00000000: 50 47 43 4f 50 59 0a ff PGCOPY.. COPY signature, flags,
-00000008: 0d 0a 00 00 00 00 00 00 ........ and extension
-00000010: 00 00 00 00 03 00 00 00 ........ Values in row
-00000018: 08 00 00 00 00 00 00 00 ........ Length of value
-00000020: 01 00 00 00 03 66 6f 6f .....foo Data
+00000000: 50 47 43 4f 50 59 0a ff PGCOPY.. COPYの署名、フラグフィールド、
+00000008: 0d 0a 00 00 00 00 00 00 ........ ヘッダ拡張領域長
+00000010: 00 00 00 00 03 00 00 00 ........ 次の行の値の数
+00000018: 08 00 00 00 00 00 00 00 ........ 次の値の長さ
+00000020: 01 00 00 00 03 66 6f 6f .....foo 値
00000028: 00 00 00 08 00 00 00 00 ........
00000030: 00 00 00 40 00 03 00 00 ...@....
00000038: 00 08 00 00 00 00 00 00 ........
@@ -127,17 +122,31 @@ Then we can annotate the actual bytes of the data based on the [documentation](h
00000078: 6e 6f 74 68 65 72 20 73 nother s
00000080: 74 72 69 6e 67 00 00 00 tring...
00000088: 08 00 00 00 00 00 00 00 ........
-00000090: 0a ff ff ... End of stream
-
-Honestly, PostgreSQL’s binary format is quite understandable, and compact at first glance. It's just a series of length-prefixed fields. But a closer look isn’t so favorable. **PostgreSQL has overheads proportional to the number of rows and columns**:
-
-* Every row has a 2 byte prefix for the number of values in the row. *But the data is tabular—we already know this info, and it doesn’t change\!*
-* Every value of every row has a 4 byte prefix for the length of the following data, or \-1 if the value is NULL. *But we know the data types, and those don’t change—plus, values of most types have a fixed, known length\!*
-* All values are big-endian. *But most of our devices are little-endian, so the data has to be converted.*
-
-For example, a single column of int32 values would have 4 bytes of data and 6 bytes of overhead per row—**60% is “wasted\!”**[^1] The ratio gets a little better with more columns (but not with more rows); in the limit we approach “only” 50% overhead. And then each of the values has to be converted (even if endian-swapping is trivial). To PostgreSQL’s credit, its format is at least cheap and easy to parse—[other formats](https://protobuf.dev/programming-guides/encoding/) get fancy with tricks like “varint” encoding which are quite expensive.
-
-How does Arrow compare? We can use [ADBC](https://arrow.apache.org/adbc/current/driver/postgresql.html) to pull the PostgreSQL table into an Arrow table, then annotate it like before:
+00000090: 0a ff ff ... ストリームの終わり00000000: ff ff ff ff d8 00 00 00 ........ IPC message length
-00000008: 10 00 00 00 00 00 0a 00 ........ IPC schema
-⋮ (208 bytes)
-000000e0: ff ff ff ff f8 00 00 00 ........ IPC message length
-000000e8: 14 00 00 00 00 00 00 00 ........ IPC record batch
-⋮ (240 bytes)
-000001e0: 01 00 00 00 00 00 00 00 ........ Data for column #1
+00000000: ff ff ff ff d8 00 00 00 ........ IPCメッセージの長さ
+00000008: 10 00 00 00 00 00 0a 00 ........ IPCスキーマ
+⋮ (208バイト)
+000000e0: ff ff ff ff f8 00 00 00 ........ IPCメッセージの長さ
+000000e8: 14 00 00 00 00 00 00 00 ........ IPCレコードバッチ
+⋮ (240バイト)
+000001e0: 01 00 00 00 00 00 00 00 ........ 1つ目の列のデータ
000001e8: 02 00 00 00 00 00 00 00 ........
000001f0: 03 00 00 00 00 00 00 00 ........
-000001f8: 00 00 00 00 03 00 00 00 ........ String offsets
+000001f8: 00 00 00 00 03 00 00 00 ........ 文字列のオフセット
00000200: 12 00 00 00 24 00 00 00 ....$...
-00000208: 66 6f 6f 61 20 6c 6f 6e fooa lon Data for column #2
+00000208: 66 6f 6f 61 20 6c 6f 6e fooa lon 2つ目の列のデータ
00000210: 67 65 72 20 73 74 72 69 ger stri
00000218: 6e 67 79 65 74 20 61 6e ngyet an
00000220: 6f 74 68 65 72 20 73 74 other st
-00000228: 72 69 6e 67 00 00 00 00 ring.... Alignment padding
-00000230: 40 00 00 00 00 00 00 00 @....... Data for column #3
+00000228: 72 69 6e 67 00 00 00 00 ring.... アラインメントのためのパッディング
+00000230: 40 00 00 00 00 00 00 00 @....... 3つ目の列のデータ
00000238: 80 00 00 00 00 00 00 00 ........
00000240: 0a 00 00 00 00 00 00 00 ........
-00000248: ff ff ff ff 00 00 00 00 ........ IPC end-of-stream
+00000248: ff ff ff ff 00 00 00 00 ........ IPCストリームの終わり00000000: 50 47 43 4f 50 59 0a ff PGCOPY.. COPYの署名、フラグフィールド、
00000008: 0d 0a 00 00 00 00 00 00 ........ ヘッダ拡張領域長
00000010: 00 00 00 00 03 00 00 00 ........ 次の行の値の数
-00000018: 08 00 00 00 00 00 00 00 ........ 次の値の長さ
+00000018: 08 00 00 00 00 00 00 00 ........ 次の値長
00000020: 01 00 00 00 03 66 6f 6f .....foo 値
00000028: 00 00 00 08 00 00 00 00 ........
00000030: 00 00 00 40 00 03 00 00 ...@....
@@ -126,12 +126,12 @@ COPY 3
正直なところ、PostgreSQLのバイナリーフォーマットは一見すると結構わかりやすくてコンパクトです。
このフォーマットは連続したフィールドだけです。
-各フィールドの前に、値の長さが置かれています。
+各フィールドの前に、値長が置かれています。
しかし、もっとよく見てみると、問題が明らかになります。
**PostgreSQLのバイナリーフォーマットはオーバーヘッドが行と列の数に比例します。**
-* 各行の前に2バイトの行の値の数が置かれています。*しかし、データは表形式ですから、値の数はもうわかっています。それに、値の数は変わりません!*
-* 各行に、各値の前に4バイトのフィールドの長さが置かれています。(NULLの場合、−1です。)*しかし、データ型はもうわかっています。それに、データ型は変わらず、データ型のほとんどは値が固定長です!*
+* 各行の前に、行内の値の数(2バイト)が置かれています。*しかし、データは表形式ですから、値の数はもうわかっています。それに、値の数は変わりません!*
+* 各行内の値の前に、フィールド長(4バイト)が置かれています。(NULLの場合、−1です。)*しかし、ほとんどのデータ型では値が固定長だし、データ型をわかっているし、データ型は変わらないし、フィールド長は大概もうわかっています!*
* すべての値はビッグエンディアンです。*しかし、現代の機器はほとんどリトルエンディアンですから、エンディアン交換が必要です。*
例えば、一つのint32の列の場合に、各行に4バイトのデータと6バイトのオーバーヘッドがあります。
@@ -139,14 +139,14 @@ COPY 3
列が増えれば増えるほど、オーバーヘッドの比率が減ります。
(しかし、行が増えればオーバーヘッドが変わりません。)
極限において、50%オーバーヘッドに近づきます。
-それから、エンディアン交換は高価な操作ではありませんが、それでも必要です。
-PostgreSQLは称賛に値するところもあります。
+エンディアン交換は高価な操作ではありませんが、それでも必要です。
+もちろん、PostgreSQLは称賛に値するところもあります。
バイナリーフォーマットは安価で解析しやすいです。
[他のフォーマット](https://protobuf.dev/programming-guides/encoding/)は「varint」エンコードなどの技術を使っています。
こういう技術は結構高価です。
Arrowはどうでしょうか?
-[ADBC](https://arrow.apache.org/adbc/current/driver/postgresql.html)によってPostgreSQLテーブルを読み込み、そして前の通りにデータに注釈を付けます。
+[ADBC](https://arrow.apache.org/adbc/current/driver/postgresql.html)でPostgreSQLテーブルを読み込み、そして前の通りにデータに注釈を付けます。
```console
>>> import adbc_driver_postgresql.dbapi
@@ -160,10 +160,10 @@ Arrowはどうでしょうか?
>>> writer.close()
```
-00000000: ff ff ff ff d8 00 00 00 ........ IPCメッセージの長さ
+00000000: ff ff ff ff d8 00 00 00 ........ IPCメッセージ長
00000008: 10 00 00 00 00 00 0a 00 ........ IPCスキーマ
⋮ (208バイト)
-000000e0: ff ff ff ff f8 00 00 00 ........ IPCメッセージの長さ
+000000e0: ff ff ff ff f8 00 00 00 ........ IPCメッセージ長
000000e8: 14 00 00 00 00 00 00 00 ........ IPCレコードバッチ
⋮ (240バイト)
000001e0: 01 00 00 00 00 00 00 00 ........ 1つ目の列のデータ
@@ -182,10 +182,10 @@ Arrowはどうでしょうか?
00000248: ff ff ff ff 00 00 00 00 ........ IPCストリームの終わり
一見すると、Arrowは結構わかりにくいです。
-データセットに全然関係なさそうなヘッダーもあり、
-まるで領域を占有するためにだけそうで謎のパッディングもあります。
-でも大事なのは、**オーバーヘッドが固定です**。
-1行でも1奥行でも、オーバーヘッドが変わりません。
+データセットに全然関係なさそうなヘッダーもあるし、
+まるで領域を占有するためにだけそうで謎のパッディングもあるし。
+しかし大事なのは、**オーバーヘッドが固定です**。
+1行でも1億行でも、オーバーヘッドが変わりません。
それに、PostgreSQLと違って**値ごとの解析は必要ありません**。
Instead of putting lengths of values everywhere, Arrow groups values of the same column (and hence same type) together, so it just needs the length of the buffer[^header]. Overhead isn't added where it isn't otherwise needed. Strings still have a length per value. Nullability is instead stored in a bitmap, which is omitted if there aren’t any NULL values (as it is here). Because of that, more rows of data doesn’t increase the overhead; instead, the more data you have, the less you pay.