DuckDB:分析用SQLite – ノートPCで数十GBのデータをスムーズに処理する極意

Database tutorial - IT technology blog
Database tutorial - IT technology blog

RAMと「巨大な」CSVファイルにまつわる話

先日、約2000万行のユーザーログデータ(約5GBのCSVファイル)を処理する機会がありました。いつものようにJupyter Notebookを開き、import pandas as pdと入力しました。しかし、数秒後にはノートPCの16GBのRAMが95%に達して赤信号。PCはフリーズし、2分間の絶望的な待ち時間の後、MemoryErrorというエラーメッセージが表示されました。

データサイエンティストにとって、このシナリオは決して珍しいものではないでしょう。Excelでは開けず、Pandasでメモリに読み込むには重すぎるファイルに直面したとき、多くの人はPostgreSQLのようなデータベースサーバーの構築を検討します。しかし、単にオフラインファイルを分析するためだけに、重厚な管理システムをインストールし、ポートやユーザー設定を行うのは、非常に時間の無駄です。

なぜ使い慣れたツールが「息切れ」してしまうのか?

適切な解決策を見つけるためには、まず従来のツールの限界を理解する必要があります。MySQL、PostgreSQL、MongoDBなどを使っていく中で、私は3つの主要な障壁に気づきました。

  • Pandasの過剰なメモリ消費: このライブラリは、データをPythonオブジェクトとしてRAMにすべて読み込みます。最適化されていない保存形式のため、1GBのCSVファイルがPandasでは4〜5GBのメモリを占有することがあります。
  • 行指向のOLTP管理システム: 特定のカラムの合計売上を計算したいだけでも、MySQLのようなDBはディスクから行全体のデータを読み取る必要があります。これが大きなI/Oボトルネックとなります。
  • SQLiteは計算向きではない: 優れた組み込みデータベースですが、依然として行指向であり、数百万件のレコードに対する集計処理(合計、平均など)には最適化されていません。

一般的な3つの解決策(とデメリット)

数十GB程度のデータを扱う際、よく検討される選択肢は以下の通りです:

  1. RAMの増設: コストがかかる上、一時的なしのぎに過ぎません。データが100GBを超えれば、どんな個人PCも耐えられなくなります。
  2. DaskやPolarsの使用: これらのライブラリは並列処理に優れています。しかし、新しい構文をゼロから学び直す必要があり、ワークフローが一時的に停滞することがあります。
  3. 専用OLAPデータベースの導入: ClickHouseやDruidは圧倒的なパフォーマンスを誇ります。一方で、個人の用途にこれらを運用するのは、まさに「牛刀をもって鶏を割く(オーバーキル)」です。

DuckDB:ローカルデータ分析の「救世主」

DuckDBは、現在最もバランスの取れた選択肢です。「分析用のSQLiteと呼ばれ、列指向(column-oriented)で動作し、Pythonプロセス内で直接実行されます。サーバーも複雑な設定も不要ですが、プロフェッショナルなデータウェアハウスのパワーを備えています。

1. 一瞬で完了するインストール

DuckDBのセットアップは非常に簡単です。Dockerやバックグラウンドサービスは不要で、コマンド1つで始められます。

pip install duckdb

2. ファイルへの直接クエリ:待ち時間なし、メモリ消費なし

DuckDBの最も価値のある機能は、インポートなしでファイルに対して直接クエリを実行できることです。ストリーミング実行メカニズムにより、結果を返すために必要なデータのみを読み込みます。

5GBのファイルから売上合計を計算するテストを、数行のコードで行ってみましょう:

import duckdb

# CSVファイルから直接クエリを実行。結果はわずか数秒で返ります
result = duckdb.query("""
    SELECT category, SUM(price) as total_revenue
    FROM 'large_data.csv'
    GROUP BY category
    ORDER BY total_revenue DESC
""").df()

print(result)

Pandasがエラーを出す一方で、DuckDBはファイルを小さなブロック(チャンク)ごとに処理します。ベクトル化実行エンジンにより、数百万行のデータを一瞬で処理できます。

3. PandasやPolarsとのスムーズな連携

通常、私はDuckDBを使って大きなデータのフィルタリングや粗集計を行います。データが軽量になった後、グラフ作成のためにPandasに渡します。DuckDBは、データコピーのコストがゼロ(Zero-copy)で、RAM上の DataFrame変数から直接読み込むことをサポートしています。

import pandas as pd
import duckdb

# SQLを活用して、DataFrame上で直接データをフィルタリング
df_raw = pd.read_csv('small_sample.csv')
refined_df = duckdb.query("SELECT * FROM df_raw WHERE price > 100").to_df()

4. データの長期保存

再利用のために中間結果を保存する必要がある場合、DuckDBはSQLiteと同様に単一のデータベースファイルに書き込むことができます。

# 接続と永続的な保存
con = duckdb.connect('analysis_report.db')

# 1つのコマンドでCSVからテーブルを作成
con.execute("CREATE TABLE sales AS SELECT * FROM read_csv_auto('sales.csv')")
con.close()

実践から得た最適化のヒント

多くのプロジェクトでDuckDBを適用してきた中で、最高のパフォーマンスを引き出すための重要なポイントをいくつか紹介します:

  • Parquet形式への変換: 列指向の最適化により、Parquetの読み込みはCSVより10〜20倍高速です。次のコマンドで素早く変換できます: COPY (SELECT * FROM 'data.csv') TO 'data.parquet' (FORMAT PARQUET);
  • read_csv_autoを活用する: この関数はデータ型とヘッダーを99%の精度で自動認識するため、手間が大幅に省けます。
  • リソース管理: スペックの低いサーバーでは、他のサービスとのメモリ競合を避けるために、SET max_memory='2GB';コマンドでRAM制限をかけましょう。
  • SQLを優先する: 同じ計算ロジックでも、DuckDB内で実行される SQLは、低レイヤーでの最適化によりPythonのループ処理よりも数倍高速です。

DuckDBは、私の個人PCでのデータ処理方法を完全に変えました。ローカルファイルの利便性と、プロフェッショナルなビッグデータシステムのパワーの間の垣根を取り払ってくれます。Pandasの待ち時間に疲れているなら、ぜひDuckDBを試してみてください。そのスピードにきっと驚くはずです。

Share: