DuckDB によるアドホック分析:数千の CSV をダッシュボードに変換する
Amazon Seller Central はほとんどのアナリストのツーリングを上回るペースでデータを生成します。トラフィックレポート、コンバージョンデータ、在庫状況サマリー、ビジネスパフォーマンスエクスポート、広告キャンペーンの内訳 – それぞれ CSV または TSV ファイルとして提供され、数ヶ月・数年にわたって数千のファイルを含むディレクトリに蓄積されます。標準的な対応はすべてをデータベースにインポートすることです。しかし現実には、本質的に読み込み集中型でスキーマが変動する分析ワークロードのために PostgreSQL インスタンスを構築・維持・クエリすることは、問題自体が正当化するより多くの運用オーバーヘッドを導入します。
私たちは 5 つの欧州マーケットプレイスで事業を展開する Amazon セラー向けのレポートパイプラインを構築しました。生データ:6 年分の Seller Central エクスポート、4,200 ファイル、ビジネスレポート、広告パフォーマンス、在庫スナップショット、FBA 手数料台帳をカバー。最初のアプローチ – ETL スクリプトを使った PostgreSQL スキーマ – は安定するまでに 3 週間かかり、Amazon が API バージョン全体でカラム名とレポート構造を改定するたびに継続的な注意が必要でした。クエリは速かった。メンテナンスはそうではなかった。
DuckDB はその計算を変えます。これは組み込み分析データベースです – 分析コードと同じプロセス内で動作し、サーバー、デーモン、接続文字列は不要で、ディスク上のファイルから直接読み込みます。クエリインターフェースは標準 SQL です。パフォーマンス特性はスケールでの集計に最適化された列型エンジンのものです。このようなワークロードでは、DuckDB は単に適切なだけでなく – 正しいツールです。
DuckDB が実際に何であるか
最も重要な区別は列型ストレージと行型ストレージです。PostgreSQL はデータを行で整理します:特定のレコードのすべてのフィールドがディスク上に連続して保存されます。SELECT revenue, units_sold FROM orders WHERE marketplace = 'DE' を実行すると、データベースはすべての行のすべてのフィールドを読み込み、要求しなかったものを破棄し、要求した 2 つを返します。これはトランザクション型ワークロード – 個別のレコードの挿入、更新、取得 – には効率的です。数百万行にわたって集計しながらほんの一握りのカラムしか触れない分析クエリには基本的に不向きです。
DuckDB はデータをカラムで整理します。revenue のすべての値が一緒に保存され;units_sold のすべての値が一緒に保存されます。同じクエリは必要な 2 つのカラムチャンクのみを読み込み、残りを完全にスキップします。40 カラムのデータセットでは、これは小さな最適化ではありません – 典型的な分析クエリで I/O を約 95% 削減します。ベクトル化実行 – 1 行ずつではなく SIMD CPU 命令を使用して一度に 1,024 値のバッチでデータを処理する – と組み合わせることで、DuckDB は同一ハードウェアで優れた PostgreSQL インスタンスが提供できるものを定期的に超える分析ワークロードのクエリパフォーマンスを達成します。
組み込みアーキテクチャも同様に重要です。ネットワークのラウンドトリップなし、認証オーバーヘッドなし、管理する接続プールなし。DuckDB はファイルまたはディレクトリを開き、クエリプランを構築して実行します。バイナリは 100MB 以下。ラップトップ、Lambda 関数の内部、cron ジョブから呼び出される Python スクリプト内で動作します。セルフホスティングの分析パイプラインでは、運用上の表面積はほぼゼロです。
ファイルに直接 SQL を実行する
DuckDB を特に Seller Central データに対して実用的にする機能はグロブベースのファイルスキャンです。単一の SQL ステートメントが数千の CSV を同時にクエリします:
SELECT
date_trunc('month', "Date") AS month,
SUM("Units Ordered") AS units,
SUM("Ordered Product Sales") AS revenue
FROM read_csv_auto('/data/seller-central/business-reports/*.csv')
GROUP BY 1
ORDER BY 1;
read_csv_auto はファイルの内容からスキーマを推論します – 行をサンプリングし、カラム型を検出し、異なる区切り文字を処理し、カラムセットが異なるファイル間でスキーマをマージします。Amazon が過去 3 年間でカラム名を少なくとも 4 回改定した Seller Central データでは、これが重要です。クエリは実行時にディレクトリに存在するファイルに対して実行され、インポートステップなし、スキーマ移行なし、無効化すべきステールキャッシュなしです。
広告コンソールからの TSV エクスポートでは、明示的なフォーマットヒントを使った同じアプローチが適用されます:
FROM read_csv('/data/advertising/*.tsv', delim=' ', header=true)
これは利便性機能ではありません。ファイルベースのデータを扱う別のメンタルモデルです:ファイルシステムがデータベースになります。スキーマは宣言されるのではなく推論されます。クエリは前処理ステップなしでファイル境界をまたいで構成されます。ETL パイプラインを更新できるより早く質問が変わるアドホック分析では、このアーキテクチャが正しいデフォルトです。
パフォーマンス特性
私たちの参照データセット – 4,200 ファイル、非圧縮で約 2.8GB – では、マーケットプレイス、ASIN、月でグループ化された完全な収益集計が標準ラップトップ(Apple M2、16GB RAM)で 4.1 秒で完了します。インポートされインデックスされたデータに対する同等の PostgreSQL クエリは 2.8 秒で完了します。しかしこの比較には 40 分の初期インポート、Amazon がカラム名を変更するたびに必要なスキーマ移行、進行中の ETL メンテナンスコストが含まれていません。総運用コストでは、このワークロードで DuckDB が決定的に勝ります。
クエリパフォーマンスがボトルネックになる場合 – 通常マルチテラバイトスケールで – DuckDB は Parquet をネイティブに読み込み、既存の CSV ファイルを Parquet に変換するのは一回限りの操作です:
COPY (FROM read_csv_auto('/data/**/*.csv'))
TO '/data/consolidated.parquet'
(FORMAT PARQUET);
Parquet ファイルに対する後続のクエリは元の CSV に対するものより 8〜12 倍速く動作します。Parquet は列統計 – 行グループあたりの最小値と最大値 – を保存し、DuckDB はそれを使ってデータのセクション全体を読み込まずにスキップします。ソートされた Parquet ファイル上の marketplace = 'DE' フィルタはディスク上のデータの 10% 未満を読み込むかもしれません。このプレディケートプッシュダウンはフラットな CSV ファイルに対しては利用できません。
Chart.js を使ったスタンドアロンダッシュボードの構築
DuckDB クエリの出力はデータです。実際の質問はそれをどう扱うかです。中小規模の Amazon オペレーションの内部レポートでは、Chart.js を使ったスタンドアロン HTML ファイルがホスト型ダッシュボードよりしばしば有用です – 実行するサーバーなし、管理する認証情報なし、維持する Superset や Metabase インスタンスなし。
パイプラインは 3 つのステップで動作します。まず、DuckDB はクエリ結果を JSON にエクスポートします:
COPY (
SELECT
strftime(date_trunc('week', "Date"), '%Y-%m-%d') AS week,
SUM("Units Ordered") AS units,
SUM("Ordered Product Sales") AS revenue
FROM read_csv_auto('/data/**/*.csv')
GROUP BY 1 ORDER BY 1
) TO 'weekly.json' (FORMAT JSON, ARRAY true);
次に、Python スクリプトがそれらの JSON ファイルを読み込み、CDN 経由で Chart.js を参照する HTML テンプレートにデータを直接埋め込みます。最後に、HTML ファイルが Web サーバー不要でブラウザで開きます。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Seller Dashboard</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<canvas id="revenue"></canvas>
<script>
const rows = /* JSON_DATA_HERE */;
new Chart(document.getElementById('revenue'), {
type: 'line',
data: {
labels: rows.map(r => r.week),
datasets: [{ label: 'Revenue', data: rows.map(r => r.revenue), tension: 0.3 }]
}
});
</script>
</body>
</html>
結果は、メールで共有したり、プライベート S3 バケットに配置したり、ローカルディレクトリから直接開いたりできる自己完結型ファイルです。ライブダッシュボードインフラのオーバーヘッドなしに週次レポートが必要な運用チームにとって、このアプローチは実用的でメンテナブルです。ダッシュボードはシェルスクリプトを実行して再生成されます:DuckDB が CSV をクエリし、Python が HTML を構築して完了。インフラなし、継続的なコストなし、メンテナンスの表面積なし。
DuckDB が PostgreSQL より優れている場合
DuckDB は PostgreSQL の代替ではありません。比較はワークロードが真に分析的な場合 – 大規模スキャン、集計、グループ化 – でデータが書き込みより主に読み込みである場合にのみ意味があります。PostgreSQL は並行書き込み、参照整合性、行レベルのアクセス制御、および本番アプリケーションの完全な運用要件において決定的に勝ります。アプリケーションコードによってレコードが挿入、更新、クエリされるマルチユーザーシステムにとっては正しい選択です。
DuckDB が優れている場合:
- データがすでにディスク上のファイルとして存在し、インポートステップが計画されていない
- スキーマが事前に完全に分かっていない、またはよく変更される
- クエリがアドホックでアクセスパターンが事前に予測できない
- データベースサーバーの運用オーバーヘッドがプロジェクトの長期性またはスケールによって正当化されない
Amazon Seller Central データは 4 つの基準すべてを正確に満たします。スキーマは Seller Central のリリースごとに変わります。ファイルはエクスポート後にすでにローカルに存在します。ビジネス上の質問は最適化されているものに応じて週ごとに変わります。そして代替手段 – 維持された PostgreSQL ETL パイプライン – は DuckDB が午後一時間で解決する問題のために数週間のエンジニアリング時間を表します。
実践的なルール:通常このデータを分析するために Excel または Jupyter ノートブックに手を伸ばすなら、代わりに DuckDB に手を伸ばしましょう。SQL はより表現力があり、パフォーマンスは数千行を超えるものに対して桁違いに優れており、ファイルベースのアーキテクチャはデータがすでにある場所に留まることを意味します。