DuckDB 1.1は単一プロセスで動作するOLAP(分析特化)データベースで、pip install duckdb のみで導入できる。
pandasのDataFrameをduckdb.sql("SELECT * FROM df")のように直接SQLでクエリでき、変換コストはほぼゼロ。
Parquet・CSV・JSONファイルをロードせず直接SELECTでき、メモリより大きいデータも処理可能。
列指向ストレージとベクトル化実行により、TPC-Hベンチマークでpandasに対して5〜10倍の高速化を達成。
Pythonのrelation APIによりLazy評価が可能で、Polarsのような遅延実行スタイルも書ける。
本番運用ではメモリ制限・スレッド数・拡張機能(httpfs、parquet)の設定が性能を左右する。
このページの内容
DuckDBとは何か:pandasとの違いを理解する
DuckDB 1.1のインストールと初期設定
pandas DataFrameに直接SQLを実行する
ParquetとCSVファイルを直接クエリする
なぜDuckDBはpandasより速いのか
ウィンドウ関数とCTEで複雑な集計を書く
Polarsとの連携:Arrowを経由したゼロコピー
本番運用での性能チューニングと注意点
DuckDBとは何か:pandasとの違いを理解する
DuckDB(ダックディービー)は、オランダのCWI研究所が2019年に公開したオープンソースの分析用データベースエンジンです。SQLiteが「トランザクション処理(OLTP)に最適化された組み込みRDB」だとすれば、DuckDBは「分析処理(OLAP)に最適化された組み込みRDB 」だと考えるとわかりやすいと思います。サーバープロセスを立てる必要がなく、Pythonの場合はimport duckdb 一行で利用を開始できます。
正直なところ、最初にDuckDBを触ったときは「SQLiteのOLAP版ね」くらいの印象でした。ところが実プロジェクトで1億行のParquetを処理させてみたら、pandasで20分かかっていた集計が90秒で終わってしまい、それ以来データパイプラインの主要部品として手放せなくなっています。
では、なぜpandasではなくDuckDBを使うのでしょうか。pandasは個別のDataFrameを「行と列が揃った表」としてメモリに保持し、Pythonオブジェクトとして1つずつ操作します。これに対しDuckDBは列指向ストレージ とベクトル化実行エンジン を採用し、C++で実装された関係演算(JOIN、GROUP BY、ウィンドウ関数)を直接実行します。結果として、数百万〜数億行のJOINや集計でpandasより一桁速いことが珍しくありません。
2024年に1.0がリリースされ、2026年現在はDuckDB 1.1系 が安定版となっています。バージョン1.0以降はストレージフォーマットの後方互換性が保証されており、本番環境への導入もしやすくなりました。Python版はPyPI上で月間ダウンロード数2,000万を超える 主要パッケージへと成長しています。
DuckDB 1.1のインストールと初期設定
インストールは拍子抜けするほど簡単です。Python 3.9以上の環境で次のコマンドを実行してください。
pip install "duckdb>=1.1.0" pandas pyarrow
pyarrowはParquetの読み書きとPolars連携で使うため、合わせて入れておくのがおすすめです。インストールが完了したら、対話的に動作確認しましょう。
import duckdb
# バージョン確認
print(duckdb.__version__) # 1.1.x
# インメモリでSQL実行(最も基本的な使い方)
result = duckdb.sql("SELECT 'Hello, DuckDB!' AS greeting, 42 AS answer")
result.show()
# ┌──────────────────┬────────┐
# │ greeting │ answer │
# │ varchar │ int32 │
# ├──────────────────┼────────┤
# │ Hello, DuckDB! │ 42 │
# └──────────────────┴────────┘
永続化したい場合はファイルベースの接続を作ります。SQLiteと同様、単一ファイル(.duckdb)にデータベース全体が保存されます。
# ファイルベースの接続を作る(存在しなければ作成される)
con = duckdb.connect("analytics.duckdb")
con.sql("""
CREATE TABLE IF NOT EXISTS sales (
order_id INTEGER,
customer_id INTEGER,
amount DECIMAL(10, 2),
order_date DATE
)
""")
con.sql("INSERT INTO sales VALUES (1, 100, 1980.00, '2026-06-01')")
con.sql("SELECT * FROM sales").show()
con.close()
メモ: DuckDBは:memory:を指定すると完全にメモリ上で動作します。デフォルトのduckdb.sql()はこのインメモリ接続を裏で利用しています。
pandas DataFrameに直接SQLを実行する
DuckDBの一番おいしい機能は、なんといってもpandasのDataFrameを変換なしで直接クエリできる ことです。これはPythonローカル変数として存在するDataFrameを、DuckDBがそのままスキャンしてSQLの実行対象にできるためです。pandasのpipe()によるメソッドチェーン でデータをクリーニングした後、集計だけをDuckDBで高速化する、というハイブリッドな使い方が一番現実的かなと思います。
import duckdb
import pandas as pd
# サンプルデータを作る
df = pd.DataFrame({
"user_id": [1, 1, 2, 2, 3],
"category": ["A", "B", "A", "A", "B"],
"amount": [100, 200, 150, 80, 300]
})
# DataFrame名(df)をそのままSQLのテーブル名として参照できる
result = duckdb.sql("""
SELECT
category,
COUNT(DISTINCT user_id) AS uniq_users,
SUM(amount) AS total,
AVG(amount) AS avg_amount
FROM df
GROUP BY category
ORDER BY total DESC
""").df() # .df() で結果をpandas DataFrameに戻す
print(result)
# category uniq_users total avg_amount
# 0 A 2 330 110.000000
# 1 B 2 500 250.000000
ポイントは.df()メソッドで結果をpandasに戻せること。同様に.arrow()でPyArrowテーブルに、.pl()でPolars DataFrameに変換できます。これらの変換はゼロコピー (メモリのコピーが発生しない)なので、巨大データでも一瞬で終わります。
ヒント: SQL内でPython変数を埋め込みたい場合は文字列フォーマットを避け、duckdb.sql("SELECT * FROM df WHERE amount > ?", params=[100])のようにパラメータ化してください。SQLインジェクション対策と型安全性の両方に効きます。
ParquetとCSVファイルを直接クエリする
多くのデータ分析シーンで、CSVやParquetをまずpd.read_csv()でメモリにロードしてから処理を始めるのが定番です。でもDuckDBなら、ファイルをロードせず直接SQLでクエリ できます。これにより、メモリに乗り切らない数十GBのデータでも、必要な行・列だけを読み出して集計できます。
import duckdb
# CSVを直接クエリする(ファイルはディスク上にあるまま)
duckdb.sql("""
SELECT customer_id, SUM(amount) AS total
FROM 'data/orders_2026.csv'
WHERE order_date >= '2026-01-01'
GROUP BY customer_id
ORDER BY total DESC
LIMIT 10
""").show()
# Parquetも同様(列指向なのでさらに高速)
duckdb.sql("""
SELECT region, AVG(price) AS avg_price
FROM 'data/sales/*.parquet' -- ワイルドカードで複数ファイルをまとめてスキャン
GROUP BY region
""").show()
# S3上のParquetもそのまま読める(httpfs拡張)
duckdb.sql("INSTALL httpfs; LOAD httpfs;")
duckdb.sql("""
SELECT COUNT(*) FROM 's3://my-bucket/events/2026/*.parquet'
""").show()
Parquetは列指向フォーマットで、DuckDBの実行エンジンとの相性が抜群です。SELECT col_a, col_b FROM 'file.parquet'を実行すると、DuckDBは必要な列だけをディスクから読む (projection pushdown)ため、列が100個あっても2列しか使わなければ実質的なI/Oは50分の1になります。さらにWHERE句もParquetのrow groupメタデータと突き合わせてプルーニング(predicate pushdown)されるので、ヒットしないブロックは読まれません。
DuckDBがpandasを上回る速度を出せる理由は、ざっくり4つあります。まず1つ目は列指向ストレージ 。pandasは内部的にはNumPyベースで列指向ですが、行ベースのインデックスやMultiIndexの存在により、列単位の最適化が部分的にしか効きません。2つ目はベクトル化された実行エンジン で、CPUのSIMD命令を活用して数千行を一度に処理します。
項目 DuckDB 1.1 pandas 3.0 Polars 1.x
クエリ言語 標準SQL(PostgreSQL方言) Pythonメソッド連鎖 Expression API + SQL
実行モデル ベクトル化(プル型) Eager(即時) Lazy + ベクトル化
並列実行 マルチスレッド自動 シングルスレッド主体 マルチスレッド自動
メモリ超のデータ ○(spill-to-disk) ×(基本不可) ○(streaming)
1億行のGROUP BY 約1.2秒 約12秒 約1.5秒
学習コスト 低(SQL既知者) 中 中〜高
3つ目の理由はクエリ最適化 です。DuckDBはJOIN順序を統計情報に基づいて並べ替え、フィルタ条件を可能な限りスキャンに押し下げます。pandasはユーザーが書いた順にメソッドを実行するため、開発者が手で最適化する必要があります(これがけっこう面倒)。4つ目は並列実行 で、DuckDBはデフォルトで全CPUコアを使いますが、pandasは基本的にシングルスレッドです。
2026年のTPC-Hベンチマーク(スケールファクター10、約60GBデータ)では、DuckDBは22クエリ平均でpandasの約8倍高速 、Polars と比較しても複雑なJOIN・集計を含むクエリではほぼ互角の性能を示しています。
注意: 単純な行ごとの変換(df.apply(func, axis=1)相当)ではDuckDBの優位は小さくなります。DuckDBが真価を発揮するのは「集計」「JOIN」「ウィンドウ関数」「フィルタリング」など、宣言的に書ける処理です。
ウィンドウ関数とCTEで複雑な集計を書く
DuckDBの強みは、本格的なSQLが使えることです。pandasのgroupby().rank()やshift()に相当する操作も、SQLのウィンドウ関数で簡潔に書けます。時系列分析 でよく使われる「ユーザーごとの累積購入額」や「前日比」も次のように記述できます。
import duckdb
result = duckdb.sql("""
WITH daily_sales AS (
SELECT
user_id,
order_date,
SUM(amount) AS daily_total
FROM 'data/orders_2026.parquet'
GROUP BY user_id, order_date
)
SELECT
user_id,
order_date,
daily_total,
SUM(daily_total) OVER (
PARTITION BY user_id
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_total,
daily_total - LAG(daily_total) OVER (
PARTITION BY user_id ORDER BY order_date
) AS day_over_day_diff,
RANK() OVER (
PARTITION BY order_date ORDER BY daily_total DESC
) AS daily_rank
FROM daily_sales
ORDER BY user_id, order_date
""").df()
CTE(共通テーブル式)でクエリを段階的に書けるので、可読性が高いのも嬉しいポイントです。pandasで同じ処理を書こうとするとgroupby、transform、rolling、shiftを組み合わせた数十行のコードになりがちで、半年後の自分が読むときに割と苦しい思いをします。DuckDBはまた再帰CTE (WITH RECURSIVE)にも対応しているため、階層構造や経路探索もSQLで解けます。
Polarsとの連携:Arrowを経由したゼロコピー
DuckDBとPolarsはどちらも高速な分析エンジンですが、競合関係ではなく補完関係 にあります。両者は内部表現にApache Arrowを採用しているため、相互変換にコピーが発生しません。「SQLで書くと簡潔な処理はDuckDB」「Expression APIで書きたい処理はPolars」と使い分けるのが個人的にしっくりきています。
import duckdb
import polars as pl
# Polars DataFrameを作る
pl_df = pl.DataFrame({
"product": ["A", "B", "C", "A", "B"],
"price": [100, 200, 150, 110, 220],
"qty": [3, 1, 2, 5, 4]
})
# Polars DFをDuckDBから直接クエリ(ゼロコピー)
result = duckdb.sql("""
SELECT product,
SUM(price * qty) AS revenue
FROM pl_df
GROUP BY product
ORDER BY revenue DESC
""").pl() # 結果もPolars DFで受け取る
print(result)
逆方向、つまりpl.read_database()でDuckDBのテーブルを読むこともできます。データパイプラインの中でDuckDBを「永続化レイヤー+分析エンジン」として使い、ETLのロジックをPolarsで書く、という構成が2026年のモダンなPythonデータスタックでよく見られます。詳細な比較はDuckDB公式のPythonガイド を参照してください。
本番運用での性能チューニングと注意点
DuckDBを本番のデータパイプラインに組み込む際は、いくつかの設定を明示的に指定しておくべきです。デフォルトのままでも動きますが、メモリやCPUを適切に制御しないと、本番サーバーで他のプロセスを圧迫することがあります(私も以前、ETLジョブがメモリを食い尽くしてアラートを鳴らしたことが…)。
import duckdb
con = duckdb.connect("prod.duckdb")
# 推奨される本番設定
con.sql("SET memory_limit = '8GB'") # メモリ上限
con.sql("SET threads = 4") # 並列スレッド数
con.sql("SET temp_directory = '/var/tmp/duckdb'") # spill先(メモリ超過時)
con.sql("SET preserve_insertion_order = false") # 順序保証を捨てて高速化
# 拡張機能のロード(必要な時だけ)
con.sql("INSTALL httpfs; LOAD httpfs;") # S3, HTTPSアクセス
con.sql("INSTALL json; LOAD json;") # JSON型サポート
DuckDBはシングルライター・マルチリーダー のモデルです。つまり、同じ.duckdbファイルに対して同時に複数のプロセスから書き込むことはできませんが、読み取り専用接続なら複数同時にオープン可能です。Webアプリのバックエンドで使う場合は、書き込みは1つのワーカーに集約するか、ストレージを分けるのが定石です。
もう一つの注意点はトランザクションの粒度 。大量のINSERTを1行ずつコミットすると性能が出ません。バルクロードする場合はCOPY ... FROMを使うか、ParquetからのCREATE TABLEを使うのが最速です。DuckDBのGitHubリリースページ には、各バージョンの性能改善履歴も詳しく記載されています。
最後に、DuckDB自体はOLAP特化なので、頻繁な点更新や同時多発的なトランザクションが必要なアプリケーションには向きません。そのようなユースケースではPostgreSQLやMySQLが適しています。あくまで「分析」「BI」「データパイプライン」「pandas 3.0 を補完する高速クエリエンジン」として位置付けるのが正解だと思います。
よくある質問(FAQ)
DuckDBはSQLiteの代わりになりますか?
いいえ、用途が異なります。SQLiteはトランザクション処理(OLTP)に最適化され、行単位の更新や複数同時書き込みに強いです。DuckDBは分析処理(OLAP)特化で、大量データの集計やJOINに強い反面、頻繁な点更新には向きません。BI・データ分析用途ならDuckDB、アプリケーションの永続化ストレージならSQLiteを選びましょう。
DuckDBとpandasはどちらを使うべきですか?
処理内容で使い分けます。データクリーニングや行単位の変換が中心ならpandasが書きやすく、大規模な集計・JOIN・ウィンドウ関数が中心ならDuckDBが圧倒的に速いです。実務では両者を組み合わせ、前処理はpandas、集計はDuckDBにする構成が一般的です。両者は.df()でシームレスに変換できます。
DuckDBはメモリより大きいデータを扱えますか?
はい、扱えます。DuckDBはメモリ不足時に中間結果をディスクに退避(spill-to-disk)する仕組みを持っており、メモリの数倍〜数十倍のデータでも処理可能です。SET temp_directoryでspill先を指定し、SET memory_limitで上限を制御してください。ただしSSDの書き込み性能が処理時間に影響します。
DuckDBは商用利用できますか?
はい、可能です。DuckDBはMITライセンスのオープンソースソフトウェアで、商用製品への組み込みや改変・再配布が無償で許可されています。Pythonバインディングも同じMITライセンスです。利用にあたって商用ライセンス料は発生しません。
DuckDBでS3上のParquetを直接クエリするには?
httpfs拡張を使います。INSTALL httpfs; LOAD httpfs;を実行後、SET s3_region='ap-northeast-1'などで認証情報を設定すれば、SELECT * FROM 's3://bucket/path/*.parquet'のようにそのままクエリできます。Parquetの列プルーニングとpredicate pushdownが効くため、必要なデータだけを転送できます。