ファイルフォーマットの系譜:Parquet・ORC・Arrow
分析基盤で必ず出会う Parquet・ORC・Arrow の違いが原理で分かります。行グループ・統計・Bloom フィルタによる枝刈りと、ディスク用とメモリ用の住み分けを構造から押さえられます。
- 1.Parquet/ORC は列指向のディスクフォーマットで、ファイルを行グループ(ストライプ)に分け、その中をカラムチャンクに割り、最内層に軽量符号化+汎用圧縮を適用する PAX 系の入れ子構造を取る。
- 2.各レベルの統計(min/max・件数・null 数)と任意の Bloom フィルタをフッタ・インデックスに持つため、述語と交差しない行グループやページを読まずに枝刈り(predicate pushdown)できる。これが分析クエリを桁で速くする中核。
- 3.Arrow はディスク形式ではなくインメモリの列形式で、復号後の値を固定レイアウトで持ちゼロコピー共有を狙う。Parquet/ORC は保存・転送、Arrow は処理中の表現、と役割が分かれる。
ディスクの列形式とメモリの列形式は別物
「列指向」と一括りにされがちですが、行指向と列指向の物理レイアウトには 保存用(ディスク) と 処理用(メモリ) という二つの世界があります。Parquet と ORC は前者、ディスクに置く永続フォーマットです。圧縮率と I/O 削減が最優先で、符号化・圧縮を何段も重ねます。Apache Arrow は後者、復号した値をメモリ上に並べる処理用フォーマットで、CPU が即座に触れる固定幅レイアウトを優先します。両者は競合せず、ファイルを Arrow へ読み込んで処理する、という補完関係にあります。本記事はこの三者の構造を対比します。
Parquet/ORC の入れ子構造:ファイル → 行グループ → カラムチャンク
Parquet も ORC も、列指向ストレージを巨大ファイルへ落とすために、行方向と列方向の二段で区切る PAX(Partition Attributes Across) 系のレイアウトを取ります。まずファイルを行方向に 行グループ(row group) ―― ORC では ストライプ(stripe) ―― に分割します。1つの行グループは数万〜数百万行ぶんのデータを抱え、サイズは Parquet で128MB前後が目安です。
その行グループの内部を、今度はカラム単位に割ったものが カラムチャンク(column chunk) です。1行グループには表のカラム数だけチャンクが並びます。Parquet ではカラムチャンクをさらに ページ(page) という最小読み書き単位に刻み、ページごとに符号化と圧縮を適用します。
Parquet ファイルの入れ子
ファイル
├─ 行グループ 0 (row group)
│ ├─ カラムチャンク: id → [page, page, ...]
│ ├─ カラムチャンク: user_id → [page, page, ...]
│ └─ カラムチャンク: amount → [page, page, ...]
├─ 行グループ 1
│ └─ ...
└─ フッタ (file footer): スキーマ + 各行グループ/カラムの統計・オフセット
この入れ子が効くのは、行グループが並列処理と枝刈りの単位になるからです。複数の行グループを別ワーカーが同時にスキャンでき、後述の統計で行グループ丸ごとスキップできます。一方でカラムチャンクが「カラムだけ読む」射影の単位を与えます。SUM(amount) なら amount のチャンクだけを各行グループから拾えばよく、他カラムには触れません。
Parquet も ORC も、スキーマと統計・オフセットをまとめたフッタをファイル末尾に置きます。本体を書き終えるまで各行グループの統計やサイズは確定しないため、ストリーミング書き込みと相性がよいからです。読み手はまずファイル末尾の固定長の長さフィールドを読み、そこからフッタ位置を逆算して1回シークし、メタデータを取得します。本体を読む前に「どの行グループのどのカラムが、どこに、何バイトあるか」が分かるので、必要な範囲だけを狙ってシーク・読み込みできます。
統計と Bloom フィルタ:読まずに捨てる仕組み
列指向ファイルが分析を桁で速くする本質は、圧縮よりむしろ 読まずに飛ばす(pruning) 仕組みにあります。Parquet/ORC は入れ子の各レベルに統計を持ちます。
- 行グループ/ストライプ単位: 各カラムの最小値・最大値(min/max)、件数、null 数。ORC は加えてストライプごとに索引を持ち、ストライプ内をさらに細かい行群(既定1万行)に分けた row index で min/max を保持します。
- ページ単位(Parquet): カラムチャンク先頭の page index(ColumnIndex/OffsetIndex)に、各ページの min/max とファイル内オフセットを持ちます。
述語が WHERE amount > 1000 のとき、ある行グループの amount の最大値が 900 なら、その行グループは条件を満たす行を1つも含みません。よって 復号も解凍もせず丸ごとスキップ できます。これが 述語・射影プッシュダウンの物理的な土台です。min/max による範囲枝刈りは、隣接ブロックの値域を記録する ゾーンマップ(zone map) の考え方そのものです。
ただし min/max は 範囲には強いが等値選択には弱い という限界があります。WHERE user_id = 'U739204' のような高カーディナリティ列の等値述語では、対象IDが多くの行グループの [min, max] 区間に入ってしまい、範囲だけではほとんど枝刈りできません。ここで効くのが Bloom フィルタです。
Bloom フィルタはカラムチャンク単位に持てる確率的データ構造で、「その値はこのチャンクに存在しない」を誤りなく即答できます(存在する側には偽陽性がありうる)。user_id = 'U739204' を評価するとき、各チャンクの Bloom フィルタに問い合わせ、否定が返ったチャンクは確実に対象を含まないので読み飛ばせます。min/max が苦手な高カーディナリティ等値述語を補う役回りで、Parquet・ORC とも任意機能として備えます。null 安全・偽陽性ありという性質は通常の Bloom フィルタと同じです。
最内層の符号化と圧縮:二段構え
ページ/ストリームの中身は、列指向の軽量符号化を最内層に適用し、その上から汎用圧縮を重ねる 二段構え です。下段の軽量符号化は辞書・RLE・FOR+デルタ・ビットパッキングで、型のそろった整数列を生み、復号せずに圧縮表現のまま述語評価できる性質を残します。上段の Snappy・Zstandard・gzip・LZ4 のような汎用圧縮は、その整数列に最終的なバイト圧縮を掛けて容量を詰めます。
論理カラム値
└→ 軽量符号化(辞書 / RLE / FOR+delta / bit-pack) ← 述語評価しやすい整数列に
└→ 汎用圧縮(Snappy / Zstd / gzip ...) ← 最終的な容量削減
└→ ページ / ストリームとして格納
ここで Parquet と ORC は符号化の 定義域 が少し違います。Parquet はページ単位に符号化方式を選び、辞書ページ+データページという構成で辞書符号化を第一級に扱います。ORC はカラムを present(null 有無)・data・length といった ストリーム に分解し、ストリームごとに符号化(整数列の RLE v2 など)を当てます。どちらも「ブロック(ページ/ストリーム)単位で分布に合った符号化を選ぶ」点は共通で、列全体に1方式を固定しません。
行グループを小さくすると、統計・フッタ・辞書などのメタデータの相対オーバーヘッドが増え、符号化の効きも悪くなります(ランや辞書が短くなる)。逆に大きすぎると、枝刈りの粒度が粗くなって「条件に1行でも合えば行グループ全体を読む」無駄が増え、行グループ1つをメモリへ展開するコストも上がります。128MB前後が定番なのは、HDFS/オブジェクトストレージのブロックサイズと枝刈り粒度の折り合い点だからです。
Parquet と ORC の対比
両者は思想がほぼ同じ PAX 系ですが、出自と既定が異なります。Parquet は Google の Dremel 論文に由来し、ネストした構造(配列・マップ・構造体) を definition/repetition レベルという2つの整数列で平坦に表現する仕組みを核に持ちます。ORC は Hive 由来で、ストライプ内の軽量索引と組み込み型の述語評価、ACID 風の更新(base+delta ファイル)に踏み込んでいる点が特色です。
| 観点 | Parquet | ORC |
|---|---|---|
| 行方向の区切り | 行グループ(row group) | ストライプ(stripe) |
| 枝刈りの最小粒度 | ページ(page index で min/max) | row index(既定1万行ごと) |
| 符号化の単位 | ページ(辞書ページ+データページ) | ストリーム(present/data/length 等に分解) |
| ネスト表現 | definition/repetition レベル(Dremel 由来) | 型ツリー+ストリーム分解 |
| Bloom フィルタ | 任意(カラムチャンク単位) | 任意(ストリーム/列単位) |
| 主な出自・親和 | 汎用・Spark/Arrow エコシステム | Hive・更新(base+delta)に強い |
実務では、Spark や DuckDB を中心とした汎用分析・Arrow との連携では Parquet、Hive 中心で更新やストライプ索引を活かす場面では ORC が選ばれる、という住み分けが多いです。どちらもデータウェアハウスやデータレイクの保存形式として広く使われます。
Arrow:処理中の列形式
Parquet/ORC が「保存して I/O を減らす」ためのディスク形式なのに対し、Apache Arrow は「メモリ上で復号済みの列を即座に処理する」ためのインメモリ形式です。決定的な違いは圧縮しないことにあります。Arrow は各カラムを 固定幅のバッファ列(値バッファ+null を示す validity ビットマップ、可変長なら offset バッファ)として、復号後の値をそのまま連続配置します。
Arrow の int32 カラム(論理値 [10, null, 30])
validity bitmap: 1 0 1 ← null 位置を1ビットで
value buffer : [10, ??, 30] ← 固定幅32ビットを連続配置(??はnull位置)
なぜ圧縮しないのか。理由は ランダムアクセスとゼロコピー です。i 番目の値が常に 先頭 + i × 幅 の位置にある固定レイアウトなら、復号やシフトなしに任意要素へ即アクセスでき、SIMD によるベクトル化実行にそのまま乗ります。さらにこの標準レイアウトを共有することで、プロセスやエンジンをまたいでも メモリをコピー・変換せずに渡せる(ゼロコピー)。Spark・pandas・DuckDB・Polars が同じ Arrow バッファを直接読めるのはこのためで、形式変換のコストが消えます。
典型的な分析パイプラインは、Parquet/ORC をディスクから読み、軽量符号化・汎用圧縮を復号して Arrow の列バッファへ展開し、その Arrow 上で述語評価・集計を回します。ディスクでは圧縮して I/O と容量を抑え、メモリでは復号して CPU 効率(SIMD・ランダムアクセス)を取る、という役割分担です。Arrow には永続・転送用の IPC/Feather 形式もありますが、設計の主眼はあくまで「処理中の共通メモリ表現」にあります。遅延マテリアライズとベクトル化が Arrow バッファ上で噛み合うのが現代の分析エンジンの基本形です。
系譜と分岐:年代でたどる
列指向ファイルの系譜は、行指向(NSM)と純粋列指向(DSM)の折衷である PAX の発想から、Dremel・Hive の二系統へ分岐し、処理層の Arrow へ収束していきます。
- 2001年(PAX 論文): ブロック内を行で区切りつつ内部はカラムでまとめる PAX が提案され、Parquet/ORC のレイアウトの祖型になる。
- 2010年(Dremel 論文): Google が definition/repetition レベルによるネスト列の平坦表現を発表。Parquet のネスト表現の直接の源流。
- 2013年(Parquet / ORC): Twitter+Cloudera 主導の Parquet と、Hive コミュニティ主導の ORC がほぼ同時期に登場。前者は汎用・Dremel 系、後者は Hive・ストライプ索引と更新に寄せる。
- 2016年(Apache Arrow): 各エンジンが独自に持っていた復号後のメモリ表現を統一する標準として登場。ディスク形式ではなく処理層を担い、Parquet と補完関係を築く。
まず三者の役割分担を即答できるように。Parquet/ORC=ディスクの列形式(圧縮・I/O 削減)、Arrow=メモリの列形式(無圧縮・ゼロコピー・SIMD)。構造はファイル → 行グループ(ストライプ)→ カラムチャンク → ページ(ストリーム)の入れ子で、各レベルの統計(min/max)で範囲枝刈り、Bloom フィルタで高カーディナリティ等値を枝刈りする、と説明します。最内層は軽量符号化+汎用圧縮の二段構え。「圧縮表現のまま述語評価」と「フッタにメタデータ」まで触れると上級者の解答になります。
まとめ
Parquet と ORC はファイル → 行グループ(ストライプ)→ カラムチャンク → ページ(ストリーム)という PAX 系の入れ子で列をディスクへ並べ、各レベルの統計(min/max・件数・null 数)で範囲を、任意の Bloom フィルタで高カーディナリティ等値を枝刈りします。最内層は軽量符号化+汎用圧縮の二段構えで、容量削減と「圧縮表現のまま述語評価」を両立させます。Apache Arrow はこれと競合せず、復号済みの値を固定幅で並べる無圧縮のインメモリ列形式として、ゼロコピー共有と SIMD ベクトル化を担います。保存・転送は Parquet/ORC、処理中の表現は Arrowという役割分担を押さえれば、現代の分析パイプライン全体が一本の線でつながります。土台は行指向と列指向のレイアウト差に遡って確認しておくと理解が安定します。
データベース Article
ファイルフォーマットの系譜:Parquet・ORC・Arrowを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
列指向
比較で見る軸
難易度: advanced / カテゴリ: データベース / タグ数: 5
導入後に効く点
各レベルの統計(min/max・件数・null 数)と任意の Bloom フィルタをフッタ・インデックスに持つため、述語と交差しない行グループやページを読まずに枝刈り(predicate pushdown)できる。これが分析クエリを桁で速くする中核。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- データベース
- タグ数
- 5
判断チェックリスト
- 自社の用途が「列指向 / Parquet」に近いか確認する。
- 強みである「Parquet/ORC は列指向のディスクフォーマットで、ファイルを行グループ(ストライプ)に分け、その中をカラムチャンクに割り、最内層に軽量符号化+汎用圧縮を適用する PAX 系の入れ子構造を取る。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。