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程度のデータを扱う際、よく検討される選択肢は以下の通りです:
- RAMの増設: コストがかかる上、一時的なしのぎに過ぎません。データが100GBを超えれば、どんな個人PCも耐えられなくなります。
- DaskやPolarsの使用: これらのライブラリは並列処理に優れています。しかし、新しい構文をゼロから学び直す必要があり、ワークフローが一時的に停滞することがあります。
- 専用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を試してみてください。そのスピードにきっと驚くはずです。

