From ebe1a8a55e79d47626d3d622db80d967626d1269 Mon Sep 17 00:00:00 2001 From: David Li Date: Mon, 10 Mar 2025 16:02:25 +0900 Subject: [PATCH 1/6] Add Japanese translation of Data Wants to Be Free --- .../arrow_result_transfer_series_japanese.md | 23 ++ _includes/top.html | 2 +- _posts/2025-02-28-data-wants-to-be-free.md | 3 + ...25-03-10-data-wants-to-be-free-japanese.md | 225 ++++++++++++++++++ 4 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 _includes/arrow_result_transfer_series_japanese.md create mode 100644 _posts/2025-03-10-data-wants-to-be-free-japanese.md diff --git a/_includes/arrow_result_transfer_series_japanese.md b/_includes/arrow_result_transfer_series_japanese.md new file mode 100644 index 000000000000..dc1e914e3a9c --- /dev/null +++ b/_includes/arrow_result_transfer_series_japanese.md @@ -0,0 +1,23 @@ + + +シリーズの記事: + +1. [Apache Arrowフォーマットはどのようにクエリー結果の転送を高速にしているのか]({% link _posts/2025-01-10-arrow-result-transfer-japanese.md %}) +1. [データは自由になりたい:Apache Arrowでデータ交換は高速]({% link _posts/2025-03-10-data-wants-to-be-free-japanese.md %}) diff --git a/_includes/top.html b/_includes/top.html index f31f984f8d22..6e35df6c713d 100644 --- a/_includes/top.html +++ b/_includes/top.html @@ -1,5 +1,5 @@ - + diff --git a/_posts/2025-02-28-data-wants-to-be-free.md b/_posts/2025-02-28-data-wants-to-be-free.md index 0d64be85ef00..f75a41eaa2df 100644 --- a/_posts/2025-02-28-data-wants-to-be-free.md +++ b/_posts/2025-02-28-data-wants-to-be-free.md @@ -9,6 +9,9 @@ image: path: /img/arrow-result-transfer/part-1-share-image.png height: 1200 width: 705 +translations: + - language: 日本語 + post_id: 2025-03-10-data-wants-to-be-free-japanese --- + + + +_この記事はデータベースとクエリーエンジン間のデータ交換フォーマットとしてなぜArrowが使われているのかという謎を解くシリーズの2記事目です。_ + +{% include arrow_result_transfer_series_japanese.md %} + +データ技術者として、データはよく「人質に取られた」ことをわかっています。 +データをもらい次第データを使わずに、まず時間を掛けなければいけません。 +非効率的で雑然CSVファイルを整理する時間を掛けたり、 +型落ちのクエリエンジンが数GBのデータに苦労するのを待つ時間を掛けたり、 +データがソケットを介して受信するのを待つ時間を掛けたり。 +今回はこの三目の問題を注目します。 +マルチギガビットネットワークの時代に、そもそもこの問題がまだ起こっているのはどうしてでしょうか? +間違いなく、この問題はぜったいまだ起こっています。 +[Mark RaasveldtとHannes Mühleisenの2017年の論文](https://doi.org/10.14778/3115404.3115408)[^freepdf]によって、10秒しかかからないはずのデータセットを送受信はあるデータシステムが**10分**以上をかかります[^ten]。 + +[^freepdf]: [VLDB](https://www.vldb.org/pvldb/vol10/p1022-muehleisen.pdf)から論文を無料でダウンロードできます。 +[^ten]: 論文のFigure 1に、ベースラインとしてnetcatは10秒でCSVファイルを送信にたいして、HiveとMongoDBは600秒以上がかかるのを示します。もちろん、CSVは解析されてないので、この比較は完全に平等のではありません。でも問題の規模を把握できます。 + +どうして必要より60倍以上の時間がかかるでしょうか? +[この前に論じていた通り、ツールはデータシリアライズのオーバーヘッドに悩まされています。]({% link _posts/2025-01-10-arrow-result-transfer.md %}) +でもArrowは手伝えます。 +それでもっと具体的にします:データシリアライズフォーマットの影響を示すために、PostgreSQLとArrowは同じデータをどうやってエンコードするのを比較します。 +その後、Arrow HTTPやArrow FlightなどというArrowでプロトコルを作る色々な方法を説明し、各方法の使い方も説明します。 + +## PostgreSQL対Arrow:データシリアライズ + +Let’s compare the [PostgreSQL binary +format](https://www.postgresql.org/docs/current/sql-copy.html#id-1.9.3.55.9.4) +and [Arrow +IPC](https://arrow.apache.org/docs/format/Columnar.html#serialization-and-interprocess-communication-ipc) +on the same dataset, and show how Arrow (with all the benefit of hindsight) +makes better trade-offs than its predecessors. + +When you execute a query with PostgreSQL, the client/driver uses the +PostgreSQL wire protocol to send the query and get back the result. Inside +that protocol, the result set is encoded with the PostgreSQL binary format[^textbinary]. + +[^textbinary]: There is a text format too, and that is often the default used by many clients. We won't discuss it here. + +First, we’ll create a table and fill it with data: + +``` +postgres=# CREATE TABLE demo (id BIGINT, val TEXT, val2 BIGINT); +CREATE TABLE +postgres=# INSERT INTO demo VALUES (1, 'foo', 64), (2, 'a longer string', 128), (3, 'yet another string', 10); +INSERT 0 3 +``` + +We can then use the COPY command to dump the raw binary data from PostgreSQL into a file: + +``` +postgres=# COPY demo TO '/tmp/demo.bin' WITH BINARY; +COPY 3 +``` + +Then we can annotate the actual bytes of the data based on the [documentation](https://www.postgresql.org/docs/current/sql-copy.html#id-1.9.3.55.9.4): + +
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 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: + +```console +>>> import adbc_driver_postgresql.dbapi +>>> import pyarrow.ipc +>>> conn = adbc_driver_postgresql.dbapi.connect("...") +>>> cur = conn.cursor() +>>> cur.execute("SELECT * FROM demo") +>>> data = cur.fetchallarrow() +>>> writer = pyarrow.ipc.new_stream("demo.arrows", data.schema) +>>> writer.write_table(data) +>>> writer.close() +``` + +
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
+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-stream
+ +Arrow looks quite…intimidating…at first glance. There’s a giant header that don’t seem related to our dataset at all, plus mysterious padding that seems to exist solely to take up space. But the important thing is that **the overhead is fixed**. Whether you’re transferring one row or a billion, the overhead doesn’t change. And unlike PostgreSQL, **no per-value parsing is required**. + +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. + +[^header]: That's what's being stored in that ginormous header (among other things)—the lengths of all the buffers. + +Even the header isn’t actually the disadvantage it looks like. The header contains the schema, which makes the data stream self-describing. With PostgreSQL, you need to get the schema from somewhere else. So we aren’t making an apples-to-apples comparison in the first place: PostgreSQL still has to transfer the schema, it’s just not part of the “binary format” that we’re looking at here[^binaryheader]. + +[^binaryheader]: And conversely, the PGCOPY header is specific to the COPY command we executed to get a bulk response. + +There’s actually another problem with PostgreSQL: alignment. The 2 byte field count at the start of every row means all the 8 byte integers after it are unaligned. And that requires extra effort to handle properly (e.g. explicit unaligned load idioms), lest you suffer [undefined behavior](https://port70.net/~nsz/c/c11/n1570.html#6.3.2.3p7), a performance penalty, or even a runtime error. Arrow, on the other hand, strategically adds some padding to keep data aligned, and lets you use little-endian or big-endian byte order depending on your data. And Arrow doesn’t apply expensive encodings to the data that require further parsing. So generally, **you can use Arrow data as-is without having to parse every value**. + +That’s the benefit of Arrow being a standardized data format. By using Arrow for serialization, data coming off the wire is already in Arrow format, and can furthermore be directly passed on to [DuckDB](https://duckdb.org), [pandas](https://pandas.pydata.org), [polars](https://pola.rs), [cuDF](https://docs.rapids.ai/api/cudf/stable/), [DataFusion](https://datafusion.apache.org), or any number of systems. Meanwhile, even if the PostgreSQL format addressed these problems—adding padding to align fields, using little-endian or making endianness configurable, trimming the overhead—you’d still end up having to convert the data to another format (probably Arrow) to use downstream. + +Even if you really did want to use the PostgreSQL binary format everywhere[^3], the documentation rather unfortunately points you to the C source code as the documentation. Arrow, on the other hand, has a [specification](https://github.com/apache/arrow/tree/main/format), [documentation](https://arrow.apache.org/docs/format/Columnar.html), and multiple [implementations](https://arrow.apache.org/docs/#implementations) (including third-party ones) across a dozen languages for you to pick up and use in your own applications. + +Now, we don’t mean to pick on PostgreSQL here. Obviously, PostgreSQL is a full-featured database with a storied history, a different set of goals and constraints than Arrow, and many happy users. Arrow isn’t trying to compete in that space. But their domains do intersect. PostgreSQL’s wire protocol has [become a de facto standard](https://datastation.multiprocess.io/blog/2022-02-08-the-world-of-postgresql-wire-compatibility.html), with even brand new products like Google’s AlloyDB using it, and so its design affects many projects[^4]. In fact, AlloyDB is a great example of a shiny new columnar query engine being locked behind a row-oriented client protocol from the 90s. So [Amdahl’s law](https://en.wikipedia.org/wiki/Amdahl's_law) rears its head again—optimizing the “front” and “back” of your data pipeline doesn’t matter when the middle is what's holding you back. + +## A quiver of Arrow (projects) + +So if Arrow is so great, how can we actually use it to build our own protocols? Luckily, Arrow comes with a variety of building blocks for different situations. + +* We just talked about [**Arrow IPC**](https://arrow.apache.org/docs/format/Columnar.html#serialization-and-interprocess-communication-ipc) before. Where Arrow is the in-memory format defining how arrays of data are laid out, er, in memory, Arrow IPC defines how to serialize and deserialize Arrow data so it can be sent somewhere else—whether that means being written to a file, to a socket, into a shared buffer, or otherwise. Arrow IPC organizes data as a sequence of messages, making it easy to stream over your favorite transport, like WebSockets. +* [**Arrow HTTP**](https://github.com/apache/arrow-experiments/tree/main/http) is “just” streaming Arrow IPC over HTTP. The Arrow community is working on standardizing it, so that different clients agree on how exactly to do this. There’s examples of clients and servers across several languages, how to use HTTP Range requests, using multipart/mixed requests to send combined JSON and Arrow responses, and more. While not a full protocol in and of itself, it’ll fit right in when building REST APIs. +* [**Disassociated IPC**](https://arrow.apache.org/docs/format/DissociatedIPC.html) combines Arrow IPC with advanced network transports like [UCX](https://openucx.org/) and [libfabric](https://ofiwg.github.io/libfabric/). For those who require the absolute best performance and have the specialized hardware to boot, this allows you to send Arrow data at full throttle, taking advantage of scatter-gather, Infiniband, and more. +* [**Arrow Flight SQL**](https://arrow.apache.org/docs/format/FlightSql.html) is a fully defined protocol for accessing relational databases. Think of it as an alternative to the full PostgreSQL wire protocol: it defines how to connect to a database, execute queries, fetch results, view the catalog, and so on. For database developers, Flight SQL provides a fully Arrow-native protocol with clients for several programming languages and drivers for ADBC, JDBC, and ODBC—all of which you don’t have to build yourself. +* And finally, [**ADBC**](https://arrow.apache.org/docs/format/ADBC.html) actually isn’t a protocol. Instead, it’s an API abstraction layer for working with databases (like JDBC and ODBC—bet you didn’t see that coming), that’s Arrow-native and doesn’t require transposing or converting columnar data back and forth. ADBC gives you a single API to access data from multiple databases, whether they use Flight SQL or something else under the hood, and if a conversion is absolutely necessary, ADBC handles the details so that you don’t have to build out a dozen connectors on your own. + +So to summarize: + +* If you’re *using* a database or other data system, you want [**ADBC**](https://arrow.apache.org/adbc/). +* If you’re *building* a database, you want [**Arrow Flight SQL**](https://arrow.apache.org/docs/format/FlightSql.html). +* If you’re working with specialized networking hardware (you’ll know if you are—that stuff doesn’t come cheap), you want the [**Disassociated IPC Protocol**](https://arrow.apache.org/docs/format/DissociatedIPC.html). +* If you’re *designing* a REST-ish API, you want [**Arrow HTTP**](https://github.com/apache/arrow-experiments/tree/main/http). +* And otherwise, you can roll-your-own with [**Arrow IPC**](https://arrow.apache.org/docs/format/Columnar.html#serialization-and-interprocess-communication-ipc). + +![A flowchart of the decision points.]({{ site.baseurl }}/assets/data_wants_to_be_free/flowchart.png){:class="img-responsive" width="100%"} + +## まとめ + +Existing client protocols can be wasteful. Arrow offers better efficiency and avoids design pitfalls from the past. And Arrow makes it easy to build and consume data APIs with a variety of standards like Arrow IPC, Arrow HTTP, and ADBC. By using Arrow serialization in protocols, everyone benefits from easier, faster, and simpler data access, and we can avoid accidentally holding data captive behind slow and inefficient interfaces. + +--- + +[^1]: Of course, it’s not fully wasted, as null/not-null is data as well. But for accounting purposes, we’ll be consistent and call lengths, padding, bitmaps, etc. “overhead”. + +[^2]: And if your data really benefits from heavy compression, you can always use something like Apache Parquet, which implements lots of fancy encodings to save space and can still be decoded to Arrow data reasonably quickly. + +[^3]: [And people do…](https://datastation.multiprocess.io/blog/2022-02-08-the-world-of-postgresql-wire-compatibility.html) + +[^4]: [We have some experience with the PostgreSQL wire protocol, too.](https://github.com/apache/arrow-adbc/blob/ed18b8b221af23c7b32312411da10f6532eb3488/c/driver/postgresql/copy/reader.h) From 2cf4fe3a4e7a63231c3e4b8539be7089fae48585 Mon Sep 17 00:00:00 2001 From: David Li Date: Mon, 10 Mar 2025 07:46:24 -0400 Subject: [PATCH 2/6] Apply suggestions from code review Co-authored-by: Sutou Kouhei --- ...25-03-10-data-wants-to-be-free-japanese.md | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/_posts/2025-03-10-data-wants-to-be-free-japanese.md b/_posts/2025-03-10-data-wants-to-be-free-japanese.md index 31cca87e0cd3..a5ce5696b3af 100644 --- a/_posts/2025-03-10-data-wants-to-be-free-japanese.md +++ b/_posts/2025-03-10-data-wants-to-be-free-japanese.md @@ -1,7 +1,7 @@ --- layout: post lang: ja-JP -title: "データは自由になりたい:Apache Arrowでデータ交換は高速" +title: "データは自由になりたい:Apache Arrowで高速データ交換" description: "" date: "2025-02-28 00:00:00" author: David Li, Ian Cook, Matt Topol @@ -53,28 +53,28 @@ limitations under the License. } -_この記事はデータベースとクエリーエンジン間のデータ交換フォーマットとしてなぜArrowが使われているのかという謎を解くシリーズの2記事目です。_ +_この記事はデータベースとクエリーエンジン間のデータ交換フォーマットとしてなぜArrowが使われているのかという謎を解くシリーズの2記事目です。_ {% include arrow_result_transfer_series_japanese.md %} -データ技術者として、データはよく「人質に取られた」ことをわかっています。 -データをもらい次第データを使わずに、まず時間を掛けなければいけません。 -非効率的で雑然CSVファイルを整理する時間を掛けたり、 -型落ちのクエリエンジンが数GBのデータに苦労するのを待つ時間を掛けたり、 -データがソケットを介して受信するのを待つ時間を掛けたり。 -今回はこの三目の問題を注目します。 +データ技術者として、データが「人質に取られている」とよく感じます。 +データをもらってもすぐに使うことはできません。使えるようになるまでに時間がかかります。 +非効率的でやっかいなCSVファイルを整理する時間だったり、 +型落ちのクエリエンジンが数GBのデータに苦労するのを待つ時間だったり、 +データがソケットを介して受信するのを待つ時間をだったり。 +今回はこの三番目の問題を注目します。 マルチギガビットネットワークの時代に、そもそもこの問題がまだ起こっているのはどうしてでしょうか? -間違いなく、この問題はぜったいまだ起こっています。 -[Mark RaasveldtとHannes Mühleisenの2017年の論文](https://doi.org/10.14778/3115404.3115408)[^freepdf]によって、10秒しかかからないはずのデータセットを送受信はあるデータシステムが**10分**以上をかかります[^ten]。 +間違いなく、この問題はまだ起こっています。 +[Mark RaasveldtとHannes Mühleisenの2017年の論文](https://doi.org/10.14778/3115404.3115408)[^freepdf]では、いくつかシステムは10秒しかかからないはずのデータセットの送受信に**10分**以上かかっていると指摘しています[^ten]。 [^freepdf]: [VLDB](https://www.vldb.org/pvldb/vol10/p1022-muehleisen.pdf)から論文を無料でダウンロードできます。 -[^ten]: 論文のFigure 1に、ベースラインとしてnetcatは10秒でCSVファイルを送信にたいして、HiveとMongoDBは600秒以上がかかるのを示します。もちろん、CSVは解析されてないので、この比較は完全に平等のではありません。でも問題の規模を把握できます。 +[^ten]: 論文のFigure 1では、ベースラインのnetcatは10秒でCSVファイルを送信し、HiveとMongoDBは600秒以上かかっていること示しています。もちろん、CSVは解析されていないので、この比較は完全に平等ではありません。しかし、問題の規模を把握できます。 -どうして必要より60倍以上の時間がかかるでしょうか? +どうして必要な時間より60倍以上も長い時間がかかるのでしょうか? [この前に論じていた通り、ツールはデータシリアライズのオーバーヘッドに悩まされています。]({% link _posts/2025-01-10-arrow-result-transfer.md %}) -でもArrowは手伝えます。 -それでもっと具体的にします:データシリアライズフォーマットの影響を示すために、PostgreSQLとArrowは同じデータをどうやってエンコードするのを比較します。 -その後、Arrow HTTPやArrow FlightなどというArrowでプロトコルを作る色々な方法を説明し、各方法の使い方も説明します。 +しかし、この問題はArrowで解消できます。 +それではもっと具体的な話をしましょう。データシリアライズフォーマットの影響を示すために、PostgreSQLとArrowが同じデータをどうやってエンコードするのかを比較しましょう。 +その後、Arrow HTTPやArrow FlightなどのArrowベースのプロトコルを作る色々な方法を説明し、各方法の使い方も説明します。 ## PostgreSQL対Arrow:データシリアライズ From 2940ad08426e4a65d6c21df48735768cfa28a0ed Mon Sep 17 00:00:00 2001 From: David Li Date: Mon, 10 Mar 2025 20:49:27 +0900 Subject: [PATCH 3/6] rename file --- ...ies_japanese.md => arrow-result-transfer-series-japanese.md} | 2 +- _posts/2025-01-10-arrow-result-transfer-japanese.md | 2 ++ _posts/2025-03-10-data-wants-to-be-free-japanese.md | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) rename _includes/{arrow_result_transfer_series_japanese.md => arrow-result-transfer-series-japanese.md} (87%) diff --git a/_includes/arrow_result_transfer_series_japanese.md b/_includes/arrow-result-transfer-series-japanese.md similarity index 87% rename from _includes/arrow_result_transfer_series_japanese.md rename to _includes/arrow-result-transfer-series-japanese.md index dc1e914e3a9c..8918e30bdad6 100644 --- a/_includes/arrow_result_transfer_series_japanese.md +++ b/_includes/arrow-result-transfer-series-japanese.md @@ -20,4 +20,4 @@ limitations under the License. シリーズの記事: 1. [Apache Arrowフォーマットはどのようにクエリー結果の転送を高速にしているのか]({% link _posts/2025-01-10-arrow-result-transfer-japanese.md %}) -1. [データは自由になりたい:Apache Arrowでデータ交換は高速]({% link _posts/2025-03-10-data-wants-to-be-free-japanese.md %}) +1. [データは自由になりたい:Apache Arrowで高速データ交換]({% link _posts/2025-03-10-data-wants-to-be-free-japanese.md %}) diff --git a/_posts/2025-01-10-arrow-result-transfer-japanese.md b/_posts/2025-01-10-arrow-result-transfer-japanese.md index b9722a9eb683..3545b297c93e 100644 --- a/_posts/2025-01-10-arrow-result-transfer-japanese.md +++ b/_posts/2025-01-10-arrow-result-transfer-japanese.md @@ -35,6 +35,8 @@ limitations under the License. _この記事はデータベースとクエリーエンジン間のデータ交換フォーマットとしてなぜArrowが使われているのかという謎を解くシリーズの最初の記事です。_ +{% include arrow-result-transfer-series-japanese.md %} + 「どうしてこんなに時間がかかるの?」 diff --git a/_posts/2025-03-10-data-wants-to-be-free-japanese.md b/_posts/2025-03-10-data-wants-to-be-free-japanese.md index a5ce5696b3af..9f77bd562e68 100644 --- a/_posts/2025-03-10-data-wants-to-be-free-japanese.md +++ b/_posts/2025-03-10-data-wants-to-be-free-japanese.md @@ -55,7 +55,7 @@ limitations under the License. _この記事はデータベースとクエリーエンジン間のデータ交換フォーマットとしてなぜArrowが使われているのかという謎を解くシリーズの2記事目です。_ -{% include arrow_result_transfer_series_japanese.md %} +{% include arrow-result-transfer-series-japanese.md %} データ技術者として、データが「人質に取られている」とよく感じます。 データをもらってもすぐに使うことはできません。使えるようになるまでに時間がかかります。 From b29d959367ae204324463b6c15552b8346a27304 Mon Sep 17 00:00:00 2001 From: David Li Date: Mon, 10 Mar 2025 20:47:49 -0400 Subject: [PATCH 4/6] Update _posts/2025-03-10-data-wants-to-be-free-japanese.md Co-authored-by: Sutou Kouhei --- _posts/2025-03-10-data-wants-to-be-free-japanese.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2025-03-10-data-wants-to-be-free-japanese.md b/_posts/2025-03-10-data-wants-to-be-free-japanese.md index 9f77bd562e68..f1cdd7db7096 100644 --- a/_posts/2025-03-10-data-wants-to-be-free-japanese.md +++ b/_posts/2025-03-10-data-wants-to-be-free-japanese.md @@ -62,7 +62,7 @@ _この記事はデータベースとクエリーエンジン間のデータ交 非効率的でやっかいなCSVファイルを整理する時間だったり、 型落ちのクエリエンジンが数GBのデータに苦労するのを待つ時間だったり、 データがソケットを介して受信するのを待つ時間をだったり。 -今回はこの三番目の問題を注目します。 +今回はこの三番目の問題に注目します。 マルチギガビットネットワークの時代に、そもそもこの問題がまだ起こっているのはどうしてでしょうか? 間違いなく、この問題はまだ起こっています。 [Mark RaasveldtとHannes Mühleisenの2017年の論文](https://doi.org/10.14778/3115404.3115408)[^freepdf]では、いくつかシステムは10秒しかかからないはずのデータセットの送受信に**10分**以上かかっていると指摘しています[^ten]。 From d0c85a2bc4daf6ff6ee6b44a9ec301f475bdf752 Mon Sep 17 00:00:00 2001 From: David Li Date: Thu, 13 Mar 2025 16:14:22 +0900 Subject: [PATCH 5/6] progress --- ...25-03-10-data-wants-to-be-free-japanese.md | 117 ++++++++++-------- 1 file changed, 68 insertions(+), 49 deletions(-) diff --git a/_posts/2025-03-10-data-wants-to-be-free-japanese.md b/_posts/2025-03-10-data-wants-to-be-free-japanese.md index f1cdd7db7096..01eecf15af9b 100644 --- a/_posts/2025-03-10-data-wants-to-be-free-japanese.md +++ b/_posts/2025-03-10-data-wants-to-be-free-japanese.md @@ -4,7 +4,7 @@ lang: ja-JP title: "データは自由になりたい:Apache Arrowで高速データ交換" description: "" date: "2025-02-28 00:00:00" -author: David Li, Ian Cook, Matt Topol +author: David Li, Ian Cook, Matt Topol, Sutou Kouhei [訳], David Li [訳] categories: [application] image: path: /img/arrow-result-transfer/part-1-share-image.png @@ -78,20 +78,15 @@ _この記事はデータベースとクエリーエンジン間のデータ交 ## PostgreSQL対Arrow:データシリアライズ -Let’s compare the [PostgreSQL binary -format](https://www.postgresql.org/docs/current/sql-copy.html#id-1.9.3.55.9.4) -and [Arrow -IPC](https://arrow.apache.org/docs/format/Columnar.html#serialization-and-interprocess-communication-ipc) -on the same dataset, and show how Arrow (with all the benefit of hindsight) -makes better trade-offs than its predecessors. +[PostgreSQLのバイナリーフォーマット](https://www.postgresql.jp/document/current/html/sql-copy.html#id-1.9.3.55.9.4)と[Arrow IPC](https://arrow.apache.org/docs/format/Columnar.html#serialization-and-interprocess-communication-ipc)を同じデータセットに比較します。 +この比較によって、Arrowは(後知恵で)前任者より良いトレードオフを行うのを示します。 -When you execute a query with PostgreSQL, the client/driver uses the -PostgreSQL wire protocol to send the query and get back the result. Inside -that protocol, the result set is encoded with the PostgreSQL binary format[^textbinary]. +PostgreSQLでクエリを実行すると、クライアント(すなわちドライバ)はPostgreSQLの転送プロトコルでクエリを送り、結果を受けます。 +そのプロトコルの内に、結果セットはPostgreSQLのバイナリーフォーマットでエンコードされています[^textbinary]。 -[^textbinary]: There is a text format too, and that is often the default used by many clients. We won't discuss it here. +[^textbinary]: テキストフォーマットもあります。その方はクライアントのほとんどによく使われているデフォルトです。この記事でテキストフォーマットを論じません。 -First, we’ll create a table and fill it with data: +まず、テーブルを定義し、テーブルにデータを入力します。 ``` postgres=# CREATE TABLE demo (id BIGINT, val TEXT, val2 BIGINT); @@ -100,20 +95,20 @@ postgres=# INSERT INTO demo VALUES (1, 'foo', 64), (2, 'a longer string', 128), INSERT 0 3 ``` -We can then use the COPY command to dump the raw binary data from PostgreSQL into a file: +それでCOPYコマンドによってPostgreSQLから生バイナリーデータをファイルにダンプします。 ``` postgres=# COPY demo TO '/tmp/demo.bin' WITH BINARY; COPY 3 ``` -Then we can annotate the actual bytes of the data based on the [documentation](https://www.postgresql.org/docs/current/sql-copy.html#id-1.9.3.55.9.4): +そして[文書](https://www.postgresql.jp/document/current/html/sql-copy.html#id-1.9.3.55.9.4)によってデータの実際のバイトに注釈を付けます。 -
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
+
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 ... ストリームの終わり
+ +正直なところ、PostgreSQLのバイナリーフォーマットは一見すると結構わかりやすくてコンパクトです。 +このフォーマットは連続したフィールドだけです。 +各フィールドの前に、値の長さが置かれています。 +しかし、もっとよく見てみると、問題が明らかになります。 +**PostgreSQLのバイナリーフォーマットはオーバーヘッドが行と列の数に比例します。** + +* 各行の前に2バイトの行の値の数が置かれています。*しかし、データは表形式ですから、値の数はもうわかっています。それに、値の数は変わりません!* +* 各行に、各値の前に4バイトのフィールドの長さが置かれています。(NULLの場合、−1です。)*しかし、データ型はもうわかっています。それに、データ型は変わらず、データ型のほとんどは値が固定長です!* +* すべての値はビッグエンディアンです。*しかし、現代の機器はほとんどリトルエンディアンですから、エンディアン交換が必要です。* + +例えば、一つのint32の列の場合に、各行に4バイトのデータと6バイトのオーバーヘッドがあります。 +つまり、**60%は無駄にされています!**[^1] +列が増えれば増えるほど、オーバーヘッドの比率が減ります。 +(しかし、行が増えればオーバーヘッドが変わりません。) +極限において、50%オーバーヘッドに近づきます。 +それから、エンディアン交換は高価な操作ではありませんが、それでも必要です。 +PostgreSQLは称賛に値するところもあります。 +バイナリーフォーマットは安価で解析しやすいです。 +[他のフォーマット](https://protobuf.dev/programming-guides/encoding/)は「varint」エンコードなどの技術を使っています。 +こういう技術は結構高価です。 + +Arrowはどうでしょうか? +[ADBC](https://arrow.apache.org/adbc/current/driver/postgresql.html)によってPostgreSQLテーブルを読み込み、そして前の通りにデータに注釈を付けます。 ```console >>> import adbc_driver_postgresql.dbapi @@ -151,28 +160,33 @@ How does Arrow compare? We can use [ADBC](https://arrow.apache.org/adbc/current/ >>> writer.close() ``` -
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ストリームの終わり
-Arrow looks quite…intimidating…at first glance. There’s a giant header that don’t seem related to our dataset at all, plus mysterious padding that seems to exist solely to take up space. But the important thing is that **the overhead is fixed**. Whether you’re transferring one row or a billion, the overhead doesn’t change. And unlike PostgreSQL, **no per-value parsing is required**. +一見すると、Arrowは結構わかりにくいです。 +データセットに全然関係なさそうなヘッダーもあり、 +まるで領域を占有するためにだけそうで謎のパッディングもあります。 +でも大事なのは、**オーバーヘッドが固定です**。 +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. @@ -190,7 +204,7 @@ Even if you really did want to use the PostgreSQL binary format everywhere[^3], Now, we don’t mean to pick on PostgreSQL here. Obviously, PostgreSQL is a full-featured database with a storied history, a different set of goals and constraints than Arrow, and many happy users. Arrow isn’t trying to compete in that space. But their domains do intersect. PostgreSQL’s wire protocol has [become a de facto standard](https://datastation.multiprocess.io/blog/2022-02-08-the-world-of-postgresql-wire-compatibility.html), with even brand new products like Google’s AlloyDB using it, and so its design affects many projects[^4]. In fact, AlloyDB is a great example of a shiny new columnar query engine being locked behind a row-oriented client protocol from the 90s. So [Amdahl’s law](https://en.wikipedia.org/wiki/Amdahl's_law) rears its head again—optimizing the “front” and “back” of your data pipeline doesn’t matter when the middle is what's holding you back. -## A quiver of Arrow (projects) +## 矢筒Arrowプロジェクトたち So if Arrow is so great, how can we actually use it to build our own protocols? Luckily, Arrow comes with a variety of building blocks for different situations. @@ -200,7 +214,7 @@ So if Arrow is so great, how can we actually use it to build our own protocols? * [**Arrow Flight SQL**](https://arrow.apache.org/docs/format/FlightSql.html) is a fully defined protocol for accessing relational databases. Think of it as an alternative to the full PostgreSQL wire protocol: it defines how to connect to a database, execute queries, fetch results, view the catalog, and so on. For database developers, Flight SQL provides a fully Arrow-native protocol with clients for several programming languages and drivers for ADBC, JDBC, and ODBC—all of which you don’t have to build yourself. * And finally, [**ADBC**](https://arrow.apache.org/docs/format/ADBC.html) actually isn’t a protocol. Instead, it’s an API abstraction layer for working with databases (like JDBC and ODBC—bet you didn’t see that coming), that’s Arrow-native and doesn’t require transposing or converting columnar data back and forth. ADBC gives you a single API to access data from multiple databases, whether they use Flight SQL or something else under the hood, and if a conversion is absolutely necessary, ADBC handles the details so that you don’t have to build out a dozen connectors on your own. -So to summarize: +要するに、 * If you’re *using* a database or other data system, you want [**ADBC**](https://arrow.apache.org/adbc/). * If you’re *building* a database, you want [**Arrow Flight SQL**](https://arrow.apache.org/docs/format/FlightSql.html). @@ -212,14 +226,19 @@ So to summarize: ## まとめ -Existing client protocols can be wasteful. Arrow offers better efficiency and avoids design pitfalls from the past. And Arrow makes it easy to build and consume data APIs with a variety of standards like Arrow IPC, Arrow HTTP, and ADBC. By using Arrow serialization in protocols, everyone benefits from easier, faster, and simpler data access, and we can avoid accidentally holding data captive behind slow and inefficient interfaces. +既存のクライアントプロトコルは効率が悪いです。 +Arrowの方はより良い効率が可能になり、昔のデザイン上の落とし穴も回避できます。 +それにArrowには色々な標準を使えば、データAPIを簡単に作れます。 +例えば、Arrow IPC、Arrow HTTP、ADBCなどを使えます。 +プロトコルの内にArrowを使えば、皆にデータやりとりをもっと高速で簡単になるメリットがあります。 +そしてデータを低速の悪くて低速のインタフェースに人質に取られないようになります。 --- -[^1]: Of course, it’s not fully wasted, as null/not-null is data as well. But for accounting purposes, we’ll be consistent and call lengths, padding, bitmaps, etc. “overhead”. +[^1]: もちろん、完全に無駄にされていません。NULLかでないかもデータとされています。しかし一貫しているために長、パッディング、ビットマップなどをオーバーヘッドとして数えます。 -[^2]: And if your data really benefits from heavy compression, you can always use something like Apache Parquet, which implements lots of fancy encodings to save space and can still be decoded to Arrow data reasonably quickly. +[^2]: それに、データAnd if your data really benefits from heavy compression, you can always use something like Apache Parquet, which implements lots of fancy encodings to save space and can still be decoded to Arrow data reasonably quickly. -[^3]: [And people do…](https://datastation.multiprocess.io/blog/2022-02-08-the-world-of-postgresql-wire-compatibility.html) +[^3]: [そういう人実際にいます…](https://datastation.multiprocess.io/blog/2022-02-08-the-world-of-postgresql-wire-compatibility.html) [^4]: [We have some experience with the PostgreSQL wire protocol, too.](https://github.com/apache/arrow-adbc/blob/ed18b8b221af23c7b32312411da10f6532eb3488/c/driver/postgresql/copy/reader.h) From 30540136ecba052130758409deb86b67b8273aec Mon Sep 17 00:00:00 2001 From: David Li Date: Thu, 13 Mar 2025 16:59:33 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=E3=81=BE=E3=81=9A=E5=B0=91=E3=81=97?= =?UTF-8?q?=E7=A2=BA=E8=AA=8D=E3=81=97=E3=81=BE=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...25-03-10-data-wants-to-be-free-japanese.md | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/_posts/2025-03-10-data-wants-to-be-free-japanese.md b/_posts/2025-03-10-data-wants-to-be-free-japanese.md index 01eecf15af9b..e500d9655b4e 100644 --- a/_posts/2025-03-10-data-wants-to-be-free-japanese.md +++ b/_posts/2025-03-10-data-wants-to-be-free-japanese.md @@ -79,12 +79,12 @@ _この記事はデータベースとクエリーエンジン間のデータ交 ## PostgreSQL対Arrow:データシリアライズ [PostgreSQLのバイナリーフォーマット](https://www.postgresql.jp/document/current/html/sql-copy.html#id-1.9.3.55.9.4)と[Arrow IPC](https://arrow.apache.org/docs/format/Columnar.html#serialization-and-interprocess-communication-ipc)を同じデータセットに比較します。 -この比較によって、Arrowは(後知恵で)前任者より良いトレードオフを行うのを示します。 +この比較で、Arrowは(後知恵のおかげで)前任者より適切のトレードオフを行うのを証明します。 -PostgreSQLでクエリを実行すると、クライアント(すなわちドライバ)はPostgreSQLの転送プロトコルでクエリを送り、結果を受けます。 +PostgreSQLでクエリを実行すると、クライアント(すなわちドライバ)はPostgreSQLの通信プロトコルでクエリを送り、結果を受けます。 そのプロトコルの内に、結果セットはPostgreSQLのバイナリーフォーマットでエンコードされています[^textbinary]。 -[^textbinary]: テキストフォーマットもあります。その方はクライアントのほとんどによく使われているデフォルトです。この記事でテキストフォーマットを論じません。 +[^textbinary]: テキストフォーマットもあります。クライアントはそのフォーマットをほとんど使っています。この記事でテキストフォーマットを論じません。 まず、テーブルを定義し、テーブルにデータを入力します。 @@ -95,19 +95,19 @@ postgres=# INSERT INTO demo VALUES (1, 'foo', 64), (2, 'a longer string', 128), INSERT 0 3 ``` -それでCOPYコマンドによってPostgreSQLから生バイナリーデータをファイルにダンプします。 +それでCOPYコマンドでPostgreSQLから生バイナリーデータをファイルにダンプします。 ``` postgres=# COPY demo TO '/tmp/demo.bin' WITH BINARY; COPY 3 ``` -そして[文書](https://www.postgresql.jp/document/current/html/sql-copy.html#id-1.9.3.55.9.4)によってデータの実際のバイトに注釈を付けます。 +そして[文書](https://www.postgresql.jp/document/current/html/sql-copy.html#id-1.9.3.55.9.4)の通り、データの実際のバイトに注釈を付けます。
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.