Apache DataFusionは、Rustで書かれた組み込み向けのSQLクエリエンジンです。データベースからストレージや権限管理を外した「SQLを解釈して実行する部分」だけを部品として提供し、InfluxDB 3やCloudflare R2 SQLなどがその上に作られています。本記事は2026年11月8日時点の一次情報と、datafusion-python 55.0.0(Python 3.13.14)で実行した出力をもとに、DataFusionの現在地、導入手順、性能を左右する設定を整理します。
まとめ:Apache DataFusionの要点
| 項目 | 2026年11月8日時点 |
|---|---|
| 位置づけ | ASFトップレベルプロジェクト(2024-04-16にArrowから独立) |
| 現行版(Rust) | datafusion 55.2.0(2026-10-07) |
| 必要なRust | 1.94.0以上(55.0.0で1.88.0から引き上げ) |
| 現行版(Python) | datafusion 55.0.0(2026-10-12、Python 3.10以上) |
| Spark高速化(Comet) | 1.2.0(2026-11-03、JDK 17以上) |
| 分散実行(Ballista) | 55.0.0(2026-10-06) |
| 標準で持たないもの | 永続ストレージ、汎用インデックス |
| ライセンス | Apache License 2.0 |
DataFusionは「すぐ使える分析データベース」ではなく、自作のデータ基盤にSQL実行層を組み込むための部品です。手元のParquetをSQLで集計したいだけならDuckDBのほうが早く片付きます。逆に、独自のデータソースや関数、最適化ルールを差し込みたい場合は、クエリプランナと最適化器をゼロから書かずに済む点が決め手になります。
性能面で押さえるべき点は2つあります。並列度(target_partitions)の効果はデータ量で大きく変わり、今回の実測では100万行ではほぼ差がなく、1,000万行では約6倍でした。1行だけを引く検索では、並列度よりもParquetファイルにページインデックスがあるかどうかのほうが効き、約55ミリ秒が約6ミリ秒に縮みました。
Arrowから独立したトップレベルプロジェクトと関連プロジェクト
DataFusionは2019年にApache Software Foundationへ寄贈され、当初はApache Arrowのサブプロジェクトでした。この体制は2024年に終わっています。公式ブログの告知は次のとおりです。
as of April 16, 2024 the Apache Arrow DataFusion subproject is now a top level Apache Software Foundation project(Announcing Apache Arrow DataFusion is now Apache DataFusion)
ASF理事会も2024年4月17日の会議で「Establish the Apache DataFusion Project」の決議を承認しています(ASF Board Minutes: DataFusion)。名称も Apache Arrow DataFusion から Apache DataFusion へ変わりました。「Apache Arrowプロジェクトの一部」と書かれた解説は2024年4月より前の情報です。Arrowのインメモリ形式を使う関係は今も続いています。
関連プロジェクトは名前が似ていて版番号の付け方も違うため、検索で見つけた情報がどれの話かを先に確かめてください。
| プロジェクト | 役割 | 最新版(公開日) |
|---|---|---|
| DataFusion(本体) | Rustクレート。SQLとDataFrame API | 55.2.0(2026-10-07) |
| DataFusion Python | PyPIのPythonバインディング | 55.0.0(2026-10-12) |
| DataFusion Comet | Apache Sparkのネイティブ実行プラグイン | 1.2.0(2026-11-03) |
| DataFusion Ballista | 複数ノードへの分散実行 | 55.0.0(2026-10-06) |
| datafusion-distributed | 分散実行ライブラリ(datafusion-contrib配下・ASF外) | 4.0.0(2026-08-20) |
検索語の「data fusion」には別製品も混ざります。Google Cloudの「Cloud Data Fusion」はGUIでETLパイプラインを組むマネージドサービス、YE DIGITALの「AQUA DataFusion」は2025年4月に発表された運用保守データ活用サービスで(クラウド Watchの報道)、どちらもRust製のApache DataFusionとは無関係です。
EXPLAINで実行計画を読む:投影・フィルタのプッシュダウンと16並列
EXPLAINの出力と3つの最適化
DataFusionが何をしているかは、EXPLAINの出力を見るのが近道です。次の5行のsales.csvに対して、集約クエリの実行計画を出します。
id,region,product,amount,ts
1,east,alpha,1200,2026-09-01
2,west,beta,800,2026-09-01
3,east,beta,1500,2026-09-02
4,north,alpha,400,2026-09-02
5,west,alpha,2200,2026-09-03
from datafusion import SessionContext
ctx = SessionContext()
ctx.register_csv("sales", "sales.csv")
ctx.sql("EXPLAIN SELECT region, sum(amount) FROM sales WHERE amount > 1000 GROUP BY region").show()
| logical_plan | Aggregate: groupBy=[[sales.region]], aggr=[[sum(sales.amount)]]
| | Filter: sales.amount > Int64(1000)
| | TableScan: sales projection=[region, amount], partial_filters=[sales.amount > Int64(1000)]
| physical_plan | AggregateExec: mode=FinalPartitioned, gby=[region@0 as region], aggr=[sum(sales.amount)]
| | RepartitionExec: partitioning=Hash([region@0], 16), input_partitions=16
| | AggregateExec: mode=Partial, gby=[region@0 as region], aggr=[sum(sales.amount)]
| | FilterExec: amount@1 > 1000
| | RepartitionExec: partitioning=RoundRobinBatch(16), input_partitions=1
| | DataSourceExec: file_groups={1 group: [[.../sales.csv]]}, projection=[region, amount], file_type=csv, has_header=true
読み取れることは3つです。projection=[region, amount] は、5列のうちクエリが使う2列だけを上へ渡していることを示します(投影のプッシュダウン)。partial_filters は、WHERE条件をスキャン側へ渡していることを示します。CSVは統計情報を持たないためスキャン後に FilterExec で絞りますが、Parquetなら行グループの最小値・最大値で読み飛ばしに使われます。最後の RoundRobinBatch(16) は、1本の入力を16本に分けて並列に集約していることを示します。16は既定値で、実行環境の論理コア数(今回は16)です。
EXPLAIN ANALYZEによる各段の出力行数の確認
55.0.0ではPostgreSQL風のオプション指定が加わり、METRICS 'rows' で行数の指標だけに絞れます(Apache DataFusion 55.0.0 Released)。上と同じクエリで実行すると、各ノードの出力行数が付いた計画が返ります。
ctx.sql("EXPLAIN (ANALYZE, METRICS 'rows', LEVEL summary) "
"SELECT region, sum(amount) FROM sales WHERE amount > 1000 GROUP BY region").show()
AggregateExec: mode=FinalPartitioned, ..., metrics=[output_rows=2]
RepartitionExec: partitioning=Hash([region@0], 16), ..., metrics=[output_rows=2]
AggregateExec: mode=Partial, ..., metrics=[output_rows=2, reduction_factor=66.67% (2/3)]
FilterExec: amount@1 > 1000, metrics=[output_rows=3, selectivity=60% (3/5)]
RepartitionExec: partitioning=RoundRobinBatch(16), input_partitions=1, metrics=[output_rows=5]
DataSourceExec: ..., file_type=csv, has_header=true, metrics=[output_rows=5]
5行読み、フィルタで3行に減り、集約で2グループになる流れがそのまま見えます。遅いクエリを調べるときは、まず DataSourceExec の output_rows が想定より多くないかを確認してください。読み飛ばしが効いていない箇所はここに出ます。
並列度の実測:100万行では差がなく1,000万行では約6倍
実行計画の16並列が速さにつながるかは、データ量次第です。1,000万行のParquetと、その先頭100万行を切り出したParquetを作り、target_partitions を1と16で比べました。
# 事前に pip install datafusion==55.0.0 pyarrow==26.0.1 numpy を実行
import time, statistics, numpy as np, pyarrow as pa, pyarrow.parquet as pq
from datafusion import SessionContext, SessionConfig
n = 10_000_000
ids = np.arange(n, dtype=np.int64)
t = pa.table({"id": ids,
"region": np.array(["east", "west", "north", "south"])[ids % 4],
"product": pa.array((ids % 50).astype(str)),
"amount": (ids * 37) % 10000})
pq.write_table(t, "big10m.parquet", compression="snappy") # 60,112,010バイト・行グループ10
pq.write_table(t.slice(0, 1_000_000), "big1m.parquet", compression="snappy") # 6,008,498バイト・行グループ1
def bench(ctx, q, reps):
ctx.sql(q).collect() # ウォームアップ(計測外)
ts = []
for _ in range(reps):
t0 = time.perf_counter(); ctx.sql(q).collect(); ts.append(time.perf_counter() - t0)
return statistics.median(ts)
agg = "SELECT region, count(*) AS n, sum(amount) AS total FROM big GROUP BY region ORDER BY region"
for p in (1, 16):
ctx = SessionContext(SessionConfig().with_target_partitions(p))
ctx.register_parquet("big", "big10m.parquet")
print(p, bench(ctx, agg, 5))
| データ | target_partitions=1 | 16(既定) | 差 |
|---|---|---|---|
| 100万行(行グループ1) | 46.6 ms | 48.8 ms | ほぼ同じ |
| 1,000万行(行グループ10) | 408.1 ms | 67.9 ms | 約6.0倍 |
環境はMacBook Pro 16,1(Intel Core i9-9880H、論理16コア)、datafusion-python 55.0.0、pyarrow 26.0.1です。各値はウォームアップ後の計測中央値を3ラウンド取り、その中央値を載せています。1ラウンドの計測回数は、100万行の集約が7回、1,000万行の集約が5回、次章の1行検索が15回です。掲載コードは比較の一部なので、表を再現するときは対象ファイルと並列度を切り替えて同じ計測を回してください。計測中は別プロセスが動いており、ロードアベレージは2前後でした。単発の値は数十パーセント揺れるため、比率で読んでください。
今回の100万行・行グループ1の集約では、並列度を16から1に落としても速さはほぼ変わりませんでした。データの分割と結果の合流にかかる手間が、並列で稼げる時間と相殺されるためです。1,000万行になると並列の効果が上回ります。55.0.0ではファイルを分割して並列に読む下限 repartition_file_min_size が10MiBから1MiBへ下がっており(information_schema.df_settings で1048576を確認)、小さめのファイルでも分割されやすくなっています。本番データの規模で同じ比較を一度回してから、既定値のままにするかを決めてください。
1行検索を速くする:ページインデックスの有無で約9倍
DataFusionは分析向けのエンジンですが、IDで1行だけを引く用途にも使われます。公式ブログは2026年10月24日に、こうした1行検索(point query)の高速化手順を公開しました。ファイルの並べ替え、辞書エンコードの無効化、行グループとページの設定、並列度、pushdown_filters、プリペアドステートメント、メモリアロケータの変更などを順に積み、1件あたりの応答時間を並べ替えだけの段階の1.43ミリ秒から0.18ミリ秒まで縮めています(Best Practices for Single Row Lookups (Point Queries))。
同じ論点を手元の1,000万行で確かめました。pyarrowの write_table は既定でページインデックスを書きません(pyarrow 26.0.1で引数 write_page_index の既定値は False)。そこで同じデータを、ページインデックスありとなしの2ファイルに書き分けて比べます。
pq.write_table(t, "big10m_pi.parquet", compression="snappy", write_page_index=True)
pt = "SELECT * FROM big WHERE id = 7654321"
cfg = (SessionConfig().with_target_partitions(1)
.set("datafusion.execution.parquet.pushdown_filters", "true"))
ctx = SessionContext(cfg)
ctx.register_parquet("big", "big10m_pi.parquet")
print(bench(ctx, pt, 15))
| ページインデックス | pushdown_filters | 並列度1 | 並列度16 |
|---|---|---|---|
| なし | false(既定) | 55.5 ms | 55.5 ms |
| なし | true | 44.7 ms | 44.6 ms |
| あり | false(既定) | 6.0 ms | 7.3 ms |
| あり | true | 6.3 ms | 7.7 ms |
効いたのはページインデックスです。理由は EXPLAIN ANALYZE の指標に出ます。どちらのファイルでも、行グループの最小値・最大値で10グループ中9グループは読み飛ばせています。違いはその先で、ページインデックスがないと残った1グループの約105万行をすべて読み、あると53ページ中1ページ(2万行)だけを読みます。
# ページインデックスなし(pushdown_filters=false)
DataSourceExec: ..., metrics=[output_rows=1.05 M, ..., row_groups_pruned_statistics=10 total → 1 matched,
..., page_index_pages_pruned=0 total → 0 matched, ...]
# ページインデックスあり(pushdown_filters=false)
FilterExec: id@0 = 7654321, metrics=[output_rows=1, ..., selectivity=0.01% (1/20.00 K)]
DataSourceExec: ..., metrics=[output_rows=20.00 K, ..., row_groups_pruned_statistics=10 total → 1 matched,
..., page_index_pages_pruned=53 total → 1 matched, page_index_rows_pruned=1.05 M total → 20.00 K matched, ...]
pushdown_filters=true にすると、スキャン自体が条件で絞って1行だけを返すようになり、FilterExec が計画から消えます。ただし時間の差はページインデックスがない場合に約2割縮んだ程度で、ある場合は誤差の範囲でした。並列度は、ページインデックスありの1行検索では1のほうがやや速く、公式ブログの「小さな読み取りは単一パーティションのほうが速い」という結果と同じ向きです。
実務での判断は単純です。1行検索が遅いときは、設定を触る前に、検索する列の並び順とページインデックスの有無を確認してください。今回のIDは昇順に並んでいたため各ページの値の範囲が重ならず、1ページまで絞れました。値がばらばらに並んだファイルでは、ページインデックスを付けても同じだけは読み飛ばせません。公式ブログも最初の手順にファイルの並べ替えを置いています。pyarrowで書いたファイルなら pq.ParquetFile(path).metadata.row_group(0).column(0).has_column_index で確かめられます。DataFusion自身の write_parquet で書き出したファイルにはページインデックスが付いていました。Parquetの行グループやページの構造はParquetとは?読み方・仕組み・CSVとの違いとSnappy・Zstd圧縮の実測で解説しています。
DataFusionをインストールする:Rust・Python・datafusion-cli
| 経路 | コマンド | 版(2026-11-08) | 向いている用途 |
|---|---|---|---|
| Rustクレート | cargo add datafusion | 55.2.0 | 自作アプリへの組み込み |
| Python | pip install datafusion | 55.0.0 | 分析スクリプト、検証 |
| CLI(cargo) | cargo install datafusion-cli | 55.2.0 | 対話的なSQL実行 |
| CLI(Homebrew) | brew install datafusion | 55.2.0 | macOSで手早く試す |
RustプロジェクトへのDataFusion追加と最低要求バージョン
Cargo.tomlに依存を足します。APIは非同期なので、Tokioのマルチスレッドランタイムも入れます。
[dependencies]
datafusion = "55.2"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
use datafusion::prelude::*;
#[tokio::main]
async fn main() -> datafusion::error::Result<()> {
let ctx = SessionContext::new();
ctx.register_csv("sales", "sales.csv", CsvReadOptions::new()).await?;
ctx.sql("SELECT region, sum(amount) AS total FROM sales GROUP BY region")
.await?
.show()
.await?;
Ok(())
}
このコードはrustc 1.99.1とdatafusion 55.2.0でビルドして実行し、region別の合計(west 3000、east 2700、north 400)が表形式で出力されることを確かめています。
注意点はRustの最低要求バージョンです。crates.ioのメタデータでは54系の rust-version が 1.88.0、55.0.0(2026-08-18)以降は1.94.0です。古いツールチェーンのCIでは、依存解決は通ってもビルドで止まります。Cargoの基本操作はCargo(Rust)とは?インストールとcargo installの使い方・主要コマンド一覧にまとめています。
Python版55.0.0の導入と本体とのメジャー番号の一致
PyPIのパッケージ名は datafusion です。Pythonバインディングは別リポジトリで独立してリリースされ、2026年9月時点ではRust本体55.1.0に対してPyPIは54.0.0と1メジャー遅れていました。2026年10月12日の55.0.0でメジャー番号が揃いました。55.0.0のCargo.tomlは本体を "55.1.0"(55.1.0以上の55系)で指定しており、このパッケージで書き出したParquetのメタデータ(created_by)は「datafusion version 55.2.0」でした。
Pythonが古いと、エラーを出さずに古い版が入ります。55.0.0の要件は Python 3.10以上で、macOS同梱のPython 3.9.6ではpipが条件を満たす最後の版まで遡ります。
$ /usr/bin/python3 -m venv e39 && ./e39/bin/pip install -q datafusion
$ ./e39/bin/python -c "import datafusion; print(datafusion.__version__)"
50.1.0
$ python3.13 -m venv v55 && ./v55/bin/pip install -q datafusion
$ ./v55/bin/python -c "import datafusion; print(datafusion.__version__)"
55.0.0
5メジャー古い50.1.0が入ったまま動くため、ドキュメントにある関数や設定が見つからないときは最初に datafusion.__version__ を確認してください。
datafusion-cliの導入経路とファイルパス参照
SQLを対話的に試すだけならCLIが最短です。cargoでソースからビルドする方法と、Homebrewで入れる方法があります。
cargo install datafusion-cli # crates.io の datafusion-cli 55.2.0 をビルド
brew install datafusion # formula 名は datafusion、入るコマンドは datafusion-cli
cargo版は本体をまるごとコンパイルするため、初回は時間がかかります。CLIでは SELECT * FROM 'data.parquet' のようにファイルパスをそのままテーブル名に書けます。PythonのSessionContextは既定ではこの書き方を受け付けず、「table ‘datafusion.public.foo.parquet’ not found」で失敗しました。Pythonでは register_parquet で登録してから参照するか、ctx.enable_url_table() でファイルパス参照を有効にします。
CSV・Parquet・JSONの登録とSQLによる集計
公式のFeatures一覧は、標準で読める形式をCSV・Parquet・JSON・Avro・Arrowとしています。Pythonでは形式ごとの register_* でテーブルとして登録し、SQLで参照します。
from datafusion import SessionContext
ctx = SessionContext()
ctx.register_csv("sales", "sales.csv")
ctx.sql("SELECT region, sum(amount) AS total FROM sales GROUP BY region ORDER BY total DESC").show()
# クエリ結果をParquetへ書き出し、読み直す
ctx.sql("SELECT * FROM sales").write_parquet("out_parquet")
ctx.register_parquet("sales_pq", "out_parquet")
ctx.sql("SELECT count(*) AS n, sum(amount) AS total FROM sales_pq").show()
DataFrame()
+--------+-------+
| region | total |
+--------+-------+
| west | 3000 |
| east | 2700 |
| north | 400 |
+--------+-------+
DataFrame()
+---+-------+
| n | total |
+---+-------+
| 5 | 6100 |
+---+-------+
表の前の DataFrame() は show() が出す見出し行です。show() の戻り値は None なので、結果を変数で受けたいときは collect() を使います。
JSONは1行1レコードのNDJSON(改行区切りJSON)が前提です。レコード内の入れ子はそのまま扱えます。構造体は user['name']、配列は unnest(tags) で展開できます。
# 先に次のような行を1行1件で ev.json に保存しておく
# {"id":1,"lvl":"warn","user":{"name":"a"},"tags":["x","y"]}
ctx.register_json("events", "ev.json")
ctx.sql("SELECT user['name'] AS u, count(*) n FROM events GROUP BY user['name'] ORDER BY u").show()
ctx.sql("SELECT unnest(tags) AS t, count(*) n FROM events GROUP BY t ORDER BY t").show()
55.0.0では、Parquetの各行がどのファイルの何行目かを返す input_file_name() と file_row_index() が加わりました。変更データの追跡や、集計結果がおかしいときの原因行の特定に使えます。
import pyarrow as pa
import pyarrow.parquet as pq
pq.write_table(pa.table({"column1": [100, 200]}), "foo.parquet")
ctx.register_parquet("foo", "foo.parquet")
ctx.sql("SELECT *, input_file_name(), file_row_index() FROM foo").show()
+---------+------------------------------------+------------------+
| column1 | input_file_name() | file_row_index() |
+---------+------------------------------------+------------------+
| 100 | private/tmp/df9349/run/foo.parquet | 0 |
| 200 | private/tmp/df9349/run/foo.parquet | 1 |
+---------+------------------------------------+------------------+
つまずきやすい仕様:識別子・MERGE INTO・永続化
引用符付き識別子の大文字・小文字の照合規則
DataFusionは引用符なしの識別子を小文字へ正規化し、二重引用符で囲んだ識別子は書いたとおりに照合します。引用符を付けると、列名と大文字・小文字まで一致していないと失敗します。
| 実際の列名 | SELECT REGION(引用符なし) |
SELECT "REGION"(引用符あり) |
|---|---|---|
| region(小文字) | 成功 | 失敗 |
| REGION(大文字) | 失敗 | 成功 |
55.0.0ではエラーメッセージが候補の列名と回避策を示すようになっています。
Schema error: No field named "REGION". Did you mean 'sales.region'?
Column names are case sensitive. You can use double quotes to refer to the sales.region column
or disable the datafusion.sql_parser.enable_ident_normalization configuration.
Valid fields are sales.id, sales.region, sales.product, sales.amount, sales.ts.
大文字を含む列名を保持したParquetでは、引用符を外すと逆に失敗します。引用符を一括で除去せず、エラーが示す列名に合わせて直してください。
MERGE INTOの計画対応と組み込みテーブルでの実行制約
55.0.0の告知には MERGE INTO 対応が並びますが、対象は論理計画と物理計画の段階です。告知自体が、組み込みのテーブルプロバイダはまだ実行部分を実装していないと書いています。メモリ上のテーブルに対して実行すると、次のエラーで止まりました。
ctx.sql("CREATE TABLE tgt (id INT, name VARCHAR) AS VALUES (1,'a'),(2,'b')").collect()
ctx.sql("CREATE TABLE src (id INT, name VARCHAR, deleted BOOLEAN) AS VALUES (2,'B',false),(3,'c',false)").collect()
ctx.sql("MERGE INTO tgt t USING src s ON t.id = s.id "
"WHEN MATCHED THEN UPDATE SET name = s.name "
"WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name)").collect()
DataFusion error: MERGE INTO operation on table 'tgt'
caused by
This feature is not implemented: MERGE INTO not supported for Base table
アップサートに使うなら、利用する版のTableProviderがMERGE INTOの実行フックを実装しているかを先に確認してください。IcebergやDelta Lakeに対応したライブラリでも、形式に対応していることと、このSQLを実行できることは別の話です。テーブル形式とファイル形式の関係はIcebergとParquetの違い|テーブル形式とファイル形式の関係を実機で確認で整理しています。
永続ストレージと汎用インデックスの非搭載
CREATE TABLE・INSERT・SHOW TABLES・DROP TABLEは通り、インメモリのカタログでテーブルを管理できます。CREATE TABLE AS VALUESで作ったテーブルはMemTableとしてメモリ上に保持されますが、SessionContextを閉じれば登録内容ごと消えます。次回の起動まで残すには、外部のファイルやオブジェクトストレージへ書き出します。主キーで1行を引くためのB木のような汎用インデックスもありません。前章のとおり、1行検索の速さはParquetの統計情報とページインデックスに依存します。
DuckDB・Polars・Trinoとの使い分け
| 比較軸 | Apache DataFusion | DuckDB | Polars | Trino |
|---|---|---|---|---|
| 実装言語 | Rust | C++ | Rust | Java |
| 主な形態 | 組み込み用ライブラリ | 組み込みDB | DataFrameライブラリ | 分散クエリエンジン |
| 永続ストレージ | なし | 独自形式のDBファイル | なし | なし(外部カタログ参照) |
| 主なインターフェース | SQL / DataFrame API | SQL | DataFrame API / SQL | SQL |
| 実行ノード | 単一(Ballistaで分散) | 単一プロセス | 単一プロセス | 複数ノード |
| 拡張点 | データソース・関数・最適化ルール | 拡張機能(extension) | プラグイン | コネクタ |
手元のParquetやCSVをSQLで分析したいだけならDuckDBです。DBファイルへの保存、豊富な拡張、CLIの使い勝手まで1つで揃います(DuckDBとは?特徴・用途とSQLite・PostgreSQLとの違いを実測で解説【1.5.6対応】)。DataFusionが有力になるのは、自分のシステムの中にSQL実行層を埋め込み、データソースや関数、最適化ルールを差し替えたい場合です。InfluxDB 3やCloudflare R2 SQLは、DataFusionをSQL実行層として組み込んでいる具体例です。
Polarsとは設計の中心が違います。PolarsはDataFrame APIが中心でSQLは補助のインターフェース、DataFusionはSQLの文法とクエリプランナが中心です。Pythonで表データを手早く加工するならPolars(Polarsとは?pandasとの違い・高速化の仕組みと実務での採用判断【実装目線】)、SQL方言の解釈や実行計画まで自分で制御したいならDataFusionです。
Trinoは規模の軸が違います。複数ノードで大規模なデータレイクへ既存のカタログ経由でSQLを投げる基盤で、運用込みのサーバー製品です(Trinoとは?分散SQLクエリエンジンの仕組みとPresto・Sparkとの違い【Trino 484対応】)。DataFusionを複数ノードで動かすなら、次章のBallistaかdatafusion-distributedを組み合わせます。
分散実行とSpark高速化:Ballista 55.0.0とComet 1.2.0
Ballista 55.0.0における適応的クエリ実行の既定化
BallistaはASFのDataFusionサブプロジェクトで、DataFusionを複数ノードへ展開する分散実行エンジンです。0.12.0(2024-02)の後、43.0.0から本体と同じ番号体系に揃えて開発が続いています。2026年10月の55.0.0は本体55.1.0に追随し、53.0.0で試験導入した適応的クエリ実行(AQE)を既定で有効にしました(Apache DataFusion Ballista 55.0.0 Released)。
同じ告知は、TPC-H(スケールファクタ1000、S3上のParquet、8vCPU・64GiBのエグゼキュータ32台)で22クエリの合計時間を比べています。Ballistaが502.4秒、Spark 4.1.3が898.2秒、Spark 4.1.3+Comet 1.1.0が455.1秒でした。BallistaはSpark単体より1.79倍速く、Cometを入れたSparkはBallistaより1.10倍速いという結果です。ただし告知自身が「調整済みの比較ではない」と断っています。Spark・Cometはエグゼキュータあたり16タスク、Ballistaは8タスクで動き、Cometにはエグゼキュータごとに32GiBのオフヒープメモリが追加されていました。502.4秒はパーティション列を宣言する更新後のベンチマークランナーでの値で、55.0.0同梱のランナーでは571.6秒です。採用判断では条件をそろえ、自分のクエリで測り直してください。
名前が近い datafusion-distributed はdatafusion-contrib配下の別プロジェクトでASFの管理外です(crates.ioの最新は4.0.0)。README自身が「完成品の分散エンジンではなく、それを作るためのライブラリ」と説明しており、スケジューラとエグゼキュータを備えたBallistaとは役割が違います。
Comet 1.2.0によるSpark実行の高速化と導入条件
CometはSparkの物理計画をDataFusionの物理計画に置き換えて実行するプラグインで、アプリのコード変更は要りません。1.2.0は2026年11月3日に公開され、Sparkとの結果の不一致(タイムゾーン、-0.0 と NaN、照合順序)の修正が中心です。Spark 4.2が対応版に加わり、Delta Lakeのネイティブスキャンが試験機能として入りました(Apache DataFusion Comet 1.2.0 Released)。
導入前に確認すべき制約は3つあります。どれも1.2.0のインストールガイド(Installing DataFusion Comet)に書かれています。
- JDK 17以上が必須。JDK 11 is no longer supported as of the 1.1.0 release.
- Maven Centralのjarが同梱するネイティブライブラリはLinux(amd64・arm64)用のみ。macOSはソースからのビルドが必要です。
- 対応Sparkは3.4.3・3.5.9・4.0.4・4.1.3・4.2.0。3.4は1.0.0で非推奨になり、3.5以降への移行が推奨されています。
手元のMacで試してからLinuxのクラスタに入れる、という流れはjarのままでは通りません。検証環境もLinuxのコンテナで用意するのが手戻りの少ない進め方です。
採用事例:Known Usersの掲載基準と主な利用先
公式ドキュメントの Known Users は、2026年11月3日の更新で掲載基準を明文化しました。公開リポジトリを持つプロジェクトは、直近6か月以内のコミット、100スター以上、5人以上のコントリビュータのすべてを満たすと「active」の一覧に載ります。満たさないものや活動が止まったものは「using or having used」の別枠です(Introduction | Apache DataFusion documentation)。古い解説記事の採用例を引くときは、どちらの枠にいるかで現状を判断できます。
| プロジェクト | DataFusionの使いどころ | Known Usersの枠 |
|---|---|---|
| InfluxDB 3 | SQLの実装 | active |
| Cloudflare R2 SQL | 各クエリワーカーのSQL実行 | active |
| Apache SedonaDB | 地理空間DBのクエリ処理 | active |
| Rerun | ロボティクスログの問い合わせ | active |
| dbt Fusion engine | dbtのRust製エンジン | active |
| HoraeDB | 時系列DB(2026-03-03にpodling退役) | 別枠(archived) |
| Dask SQL | Python分散SQL(PyPI最終2024.5.0) | 別枠 |
InfluxDB 3 Coreのリファレンスは InfluxDB 3 Core uses the Apache Arrow DataFusion implementation of SQL と明記しています(InfluxDB 3 SQL reference)。Cloudflareは R2 SQL の解説で Internally, each query worker uses Apache DataFusion to run SQL queries against row groups. と書き、Parquetの行グループをそのままDataFusionのパーティションとして扱う設計を説明しています(Cloudflare Blog: R2 SQL deep dive)。SedonaDBは2026年11月6日のコミット(Update to DataFusion 55.2.0 #1316)で依存を55.2.0へ上げており、本体のリリースから1か月で追随しています。
古い解説で採用例に挙がるHoraeDBとDask SQLは、いまは別枠です。HoraeDBはApache Incubatorのページに「The HoraeDB podling retired on 2026-03-03」とあり(HoraeDB Project Incubation Status)、Dask SQLはPyPIの最終版が2024.5.0、リポジトリの最終更新が2024年8月です。
DataFusionを組み込んだデータ分析基盤の設計や、Parquet・Icebergを中心にした構成の検討は、データ分析基盤構築・MLOps構築支援でご相談いただけます。
よくある質問
Apache DataFusionはApache Arrowの一部ですか?
いいえ。2024年4月16日にApache Arrowのサブプロジェクトからトップレベルプロジェクトへ独立しました。Arrowのインメモリ形式を使う関係は続いていますが、リリースもドキュメントも独立しています。
Google CloudのData FusionやAQUA DataFusionと同じものですか?
別物です。Cloud Data FusionはGoogle CloudのGUIベースのETLサービス、AQUA DataFusionはYE DIGITALの運用保守データ活用サービスです。Apache DataFusionはRust製の組み込み向けSQLクエリエンジンで、開発元も用途も重なりません。
datafusion-cliはどうやってインストールしますか?
Rust環境があれば cargo install datafusion-cli でcrates.ioの55.2.0をビルドできます。macOSなら brew install datafusion でも入り、どちらもコマンド名は datafusion-cli です。
Python版とRust版でバージョンは揃っていますか?
2026年11月8日時点では、Rust本体が55.2.0、PyPIのdatafusionが55.0.0で、メジャー番号は揃っています。Python版は別リポジトリでリリースされるため、版番号の末尾や公開日は一致しません。macOS同梱のPython 3.9.6では、エラーなしで50.1.0が入りました(50.1.0自体はPython 3.9以上が要件)。
DataFusionとDuckDBはどちらを選ぶべきですか?
手元のParquetやCSVをSQLで分析する目的ならDuckDBです。自作システムにSQL実行層を組み込み、データソースや関数、最適化ルールを自分で拡張したいならDataFusionです。Sparkの既存ジョブを速くしたい場合は、DataFusionを内部で使うCometが選択肢になります。