DuckDB 2025 - ローカル分析の革命児

2026.01.12

DuckDBとは

DuckDBは組み込み型の列指向OLAP(Online Analytical Processing)データベースです。SQLiteのように依存関係なしで動作しながら、分析クエリに特化した設計により、数十億行のデータを瞬時に処理できます。2025年、DuckDBはデータサイエンティストやアナリストの必須ツールとして確立されました。

なぜDuckDBが注目されるのか

従来の分析環境の課題

従来のワークフロー:
  ローカルCSV → pandasで読み込み → メモリ不足
  または
  ローカルCSV → クラウドDWHにアップロード → クエリ実行 → 結果ダウンロード

DuckDBのワークフロー:
  ローカルCSV/Parquet → DuckDBで直接クエリ → 即座に結果

従来、大規模データの分析にはBigQueryやSnowflakeなどのクラウドDWHが必要でした。しかし、以下の課題がありました:

  • コスト: クラウドの従量課金
  • レイテンシ: データのアップロード/ダウンロード時間
  • 複雑性: 環境セットアップとネットワーク依存
  • プライバシー: 機密データのクラウド転送

DuckDBはこれらの課題をローカル実行で解決します。

DuckDBの主要特徴

1. 組み込み型アーキテクチャ

# サーバー不要、インストールも簡単
pip install duckdb

import duckdb

# インメモリデータベース(デフォルト)
conn = duckdb.connect()

# 永続化データベース
conn = duckdb.connect('my_analysis.duckdb')

# 即座にクエリ実行可能
result = conn.execute("SELECT 42 AS answer").fetchall()
print(result)  # [(42,)]

特徴:

  • サーバープロセス不要
  • シングルファイルで完結
  • ゼロ設定で起動
  • クロスプラットフォーム対応

2. 列指向ストレージ

行指向(従来のRDBMS):
┌─────┬────────┬─────────┬──────────┐
│ ID  │ Name   │ Amount  │ Date     │
├─────┼────────┼─────────┼──────────┤
│ 1   │ Alice  │ 1000    │ 2025-01  │ → ディスク読み込み
│ 2   │ Bob    │ 2000    │ 2025-02  │ → ディスク読み込み
│ 3   │ Carol  │ 1500    │ 2025-03  │ → ディスク読み込み
└─────┴────────┴─────────┴──────────┘

列指向(DuckDB):
┌─────────────────────────────────┐
│ ID:     [1, 2, 3]               │ → 必要な列だけ読み込み
│ Name:   [Alice, Bob, Carol]     │
│ Amount: [1000, 2000, 1500]      │ ← SUM(Amount)は
│ Date:   [2025-01, 02, 03]       │   この列だけでOK
└─────────────────────────────────┘

分析クエリでの利点:

  • 集計関数(SUM, AVG, COUNT)が高速
  • 必要な列のみをスキャン
  • 圧縮効率が高い(同じ型のデータが連続)

3. ベクトル化実行エンジン

-- DuckDBは内部でベクトル演算を使用
-- 1行ずつではなく、数千行を一度に処理

SELECT
    product_category,
    SUM(sales_amount) as total_sales,
    AVG(quantity) as avg_quantity
FROM sales_data
WHERE sale_date >= '2025-01-01'
GROUP BY product_category
ORDER BY total_sales DESC;

-- ベクトル化により:
-- - CPUキャッシュを効率的に活用
-- - SIMD命令による並列演算
-- - 分岐予測の最適化

pandas/Polars統合

pandasとの連携

import duckdb
import pandas as pd

# pandasのDataFrameを直接クエリ
df = pd.DataFrame({
    'customer_id': range(1, 1000001),
    'amount': [i * 100 for i in range(1, 1000001)],
    'category': ['A', 'B', 'C'] * 333333 + ['A']
})

# pandasより10-100倍高速なクエリ
result = duckdb.query("""
    SELECT
        category,
        COUNT(*) as count,
        SUM(amount) as total,
        AVG(amount) as average
    FROM df
    GROUP BY category
    ORDER BY total DESC
""").df()

print(result)
#   category   count        total     average
# 0        A  333334  16666850000   50000.25
# 1        C  333333  16666650000   50000.00
# 2        B  333333  16666550000   49999.50

大規模DataFrameの処理

# pandasでは困難な大規模データも処理可能
# DuckDBはディスクスピルを自動で処理

# 10GB以上のCSVを直接クエリ
result = duckdb.query("""
    SELECT
        date_trunc('month', sale_date) as month,
        region,
        SUM(revenue) as monthly_revenue
    FROM 'sales_2025_*.csv'  -- ワイルドカードで複数ファイル
    GROUP BY 1, 2
    ORDER BY 1, monthly_revenue DESC
""").df()

Polarsとの連携

import duckdb
import polars as pl

# PolarsのLazyFrameをDuckDBでクエリ
lf = pl.scan_parquet("large_dataset/*.parquet")

# DuckDBがPolarsのLazyFrameを認識
result = duckdb.query("""
    SELECT
        product_id,
        SUM(quantity) as total_quantity
    FROM lf
    WHERE price > 100
    GROUP BY product_id
    HAVING SUM(quantity) > 1000
""").pl()  # Polars DataFrameとして返却

# または相互変換
duckdb_result = duckdb.query("SELECT * FROM lf LIMIT 10")
polars_df = duckdb_result.pl()

Parquet/CSV直接クエリ

ファイル直接クエリの威力

import duckdb

# CSVを直接クエリ(ロード不要)
result = duckdb.query("""
    SELECT
        city,
        COUNT(*) as transactions,
        SUM(amount) as total_amount
    FROM 'transactions.csv'
    GROUP BY city
    ORDER BY total_amount DESC
    LIMIT 10
""").df()

# Parquetファイルも同様
result = duckdb.query("""
    SELECT * FROM 'data/sales_2025.parquet'
    WHERE region = 'APAC'
""").df()

# 複数ファイルをワイルドカードで指定
result = duckdb.query("""
    SELECT
        filename,
        COUNT(*) as row_count
    FROM 'data/logs_*.parquet'
    GROUP BY filename
""").df()

# GCSやS3のファイルも直接クエリ可能
result = duckdb.query("""
    SELECT * FROM 's3://my-bucket/data/*.parquet'
    WHERE event_type = 'purchase'
""").df()

高度なファイル操作

# JSONファイルのクエリ
result = duckdb.query("""
    SELECT
        json_extract_string(data, '$.user.name') as user_name,
        json_extract(data, '$.items') as items
    FROM read_json_auto('events.json')
""").df()

# Excel ファイルの読み込み
duckdb.install_extension('spatial')
duckdb.load_extension('spatial')

result = duckdb.query("""
    SELECT * FROM st_read('data.xlsx', layer='Sheet1')
""").df()

# 異なるフォーマット間の変換
duckdb.query("""
    COPY (SELECT * FROM 'input.csv' WHERE amount > 1000)
    TO 'output.parquet' (FORMAT PARQUET, COMPRESSION ZSTD)
""")

MotherDuckクラウドサービス

MotherDuckとは

MotherDuckは、DuckDBをクラウドで利用できるサーバーレス分析プラットフォームです。ローカルDuckDBの利便性を維持しながら、チーム共有と大規模データ処理を実現します。

import duckdb

# MotherDuckへの接続
conn = duckdb.connect("md:my_database?motherduck_token=YOUR_TOKEN")

# ローカルとクラウドのハイブリッドクエリ
result = conn.execute("""
    SELECT
        local_table.id,
        cloud_table.name,
        local_table.value
    FROM local_data.csv AS local_table
    JOIN my_database.customers AS cloud_table
    ON local_table.customer_id = cloud_table.id
""").fetchdf()

MotherDuckの主要機能

機能:
  共有データベース:
    - チームでのデータ共有
    - アクセス制御
    - バージョニング

  ハイブリッドクエリ:
    - ローカルデータ + クラウドデータ
    - 自動的なワークロード分散
    - ネットワーク転送の最小化

  スケーラビリティ:
    - サーバーレスで自動スケール
    - TB級のデータ処理
    - 従量課金制

料金体系(2025年現在):
  無料枠: 10GB/月
  Pro: $0.50/GB scanned
  Enterprise: カスタム

コラボレーション機能

# データの共有
conn.execute("""
    CREATE OR REPLACE SHARE my_analytics_share AS
    SELECT * FROM processed_sales
    WHERE date >= '2025-01-01'
""")

# 共有データへのアクセス
other_conn = duckdb.connect("md:?motherduck_token=OTHER_USER_TOKEN")
result = other_conn.execute("""
    SELECT * FROM attach_share('organization/my_analytics_share')
""").fetchdf()

実践的なコード例

データパイプライン構築

import duckdb

def create_analytics_pipeline():
    conn = duckdb.connect('analytics.duckdb')

    # ステージングテーブルの作成
    conn.execute("""
        CREATE OR REPLACE TABLE stg_orders AS
        SELECT
            order_id,
            customer_id,
            CAST(order_date AS DATE) as order_date,
            CAST(amount AS DECIMAL(10,2)) as amount,
            status
        FROM 'raw_data/orders_*.csv'
        WHERE order_date IS NOT NULL
    """)

    # 集計テーブルの作成
    conn.execute("""
        CREATE OR REPLACE TABLE daily_sales AS
        SELECT
            order_date,
            COUNT(DISTINCT order_id) as orders,
            COUNT(DISTINCT customer_id) as customers,
            SUM(amount) as revenue,
            AVG(amount) as avg_order_value
        FROM stg_orders
        WHERE status = 'completed'
        GROUP BY order_date
    """)

    # 移動平均の計算
    conn.execute("""
        CREATE OR REPLACE TABLE sales_trends AS
        SELECT
            order_date,
            revenue,
            AVG(revenue) OVER (
                ORDER BY order_date
                ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
            ) as revenue_7day_ma,
            AVG(revenue) OVER (
                ORDER BY order_date
                ROWS BETWEEN 29 PRECEDING AND CURRENT ROW
            ) as revenue_30day_ma
        FROM daily_sales
        ORDER BY order_date
    """)

    return conn

# パイプライン実行
conn = create_analytics_pipeline()
trends = conn.execute("SELECT * FROM sales_trends").df()

高度な分析クエリ

-- コホート分析
WITH first_purchase AS (
    SELECT
        customer_id,
        DATE_TRUNC('month', MIN(order_date)) as cohort_month
    FROM orders
    GROUP BY customer_id
),
monthly_activity AS (
    SELECT
        o.customer_id,
        fp.cohort_month,
        DATE_TRUNC('month', o.order_date) as activity_month,
        DATE_DIFF('month', fp.cohort_month, DATE_TRUNC('month', o.order_date)) as months_since_first
    FROM orders o
    JOIN first_purchase fp ON o.customer_id = fp.customer_id
)
SELECT
    cohort_month,
    months_since_first,
    COUNT(DISTINCT customer_id) as active_customers,
    ROUND(COUNT(DISTINCT customer_id) * 100.0 /
        FIRST_VALUE(COUNT(DISTINCT customer_id)) OVER (
            PARTITION BY cohort_month
            ORDER BY months_since_first
        ), 1) as retention_rate
FROM monthly_activity
GROUP BY cohort_month, months_since_first
ORDER BY cohort_month, months_since_first;

-- RFM分析
WITH customer_rfm AS (
    SELECT
        customer_id,
        DATE_DIFF('day', MAX(order_date), CURRENT_DATE) as recency,
        COUNT(DISTINCT order_id) as frequency,
        SUM(amount) as monetary
    FROM orders
    WHERE order_date >= CURRENT_DATE - INTERVAL '1 year'
    GROUP BY customer_id
),
rfm_scores AS (
    SELECT
        customer_id,
        recency,
        frequency,
        monetary,
        NTILE(5) OVER (ORDER BY recency DESC) as r_score,
        NTILE(5) OVER (ORDER BY frequency) as f_score,
        NTILE(5) OVER (ORDER BY monetary) as m_score
    FROM customer_rfm
)
SELECT
    customer_id,
    recency,
    frequency,
    monetary,
    r_score || f_score || m_score as rfm_segment,
    CASE
        WHEN r_score >= 4 AND f_score >= 4 THEN 'Champions'
        WHEN r_score >= 4 AND f_score <= 2 THEN 'New Customers'
        WHEN r_score <= 2 AND f_score >= 4 THEN 'At Risk'
        WHEN r_score <= 2 AND f_score <= 2 THEN 'Lost'
        ELSE 'Regular'
    END as customer_segment
FROM rfm_scores
ORDER BY monetary DESC;

Jupyterでの活用

# Jupyter Notebook での使用例
import duckdb
import matplotlib.pyplot as plt

# マジックコマンドの設定
%load_ext duckdb_magic

# セルでSQLを直接実行
%%duckdb
SELECT
    DATE_TRUNC('week', order_date) as week,
    SUM(amount) as weekly_revenue
FROM 'sales.parquet'
GROUP BY 1
ORDER BY 1

# 結果を変数に格納
%%duckdb result_df <<
SELECT
    category,
    COUNT(*) as count,
    SUM(revenue) as total_revenue
FROM sales
GROUP BY category

# 可視化
result_df.plot(kind='bar', x='category', y='total_revenue')
plt.title('Revenue by Category')
plt.show()

BigQuery/Snowflakeとの使い分け

比較表

| 観点 | DuckDB | BigQuery | Snowflake |
|------|--------|----------|-----------|
| デプロイ | 組み込み | クラウド | クラウド |
| コスト | 無料 | 従量課金 | 従量課金 |
| セットアップ | 数秒 | 数分 | 数分-数時間 |
| データサイズ | GB-数百GB | TB-PB | TB-PB |
| 同時ユーザー | シングル | 数千 | 数千 |
| レイテンシ | ミリ秒 | 秒-分 | 秒-分 |
| ユースケース | ローカル分析、開発 | 本番DWH | 本番DWH |

使い分けガイド

DuckDBが最適な場面:
  - ローカルでの探索的データ分析
  - データサイエンスのプロトタイピング
  - CI/CDパイプラインでのデータテスト
  - 機密データの分析(クラウド転送不要)
  - コスト最適化(小-中規模データ)
  - 組み込みアプリケーション

BigQuery/Snowflakeが最適な場面:
  - TB/PB級の大規模データ
  - 複数チームでの同時アクセス
  - 本番環境のダッシュボード
  - リアルタイムストリーミング
  - エンタープライズガバナンス

ハイブリッドアプローチ:
  開発/テスト: DuckDB
  ↓
  本番/共有: BigQuery/Snowflake
  ↓
  ローカル分析: DuckDB(サンプルデータ)

ハイブリッド実装例

# 開発時はDuckDB、本番はBigQueryへのシームレスな移行

class DataWarehouse:
    def __init__(self, environment='dev'):
        self.environment = environment
        if environment == 'dev':
            self.conn = duckdb.connect()
        else:
            from google.cloud import bigquery
            self.conn = bigquery.Client()

    def query(self, sql):
        if self.environment == 'dev':
            # DuckDBでローカル実行
            return self.conn.execute(sql).df()
        else:
            # BigQueryで実行
            return self.conn.query(sql).to_dataframe()

# 使用例
dw = DataWarehouse(environment='dev')  # 開発時
# dw = DataWarehouse(environment='prod')  # 本番時

result = dw.query("""
    SELECT region, SUM(sales) as total_sales
    FROM sales_data
    GROUP BY region
""")

2025年の採用状況

急成長するエコシステム

統計(2025年):
  GitHub Stars: 25,000+
  月間ダウンロード: 15M+
  企業採用: Fortune 500の30%以上

主要な採用企業:
  - Netflix: ログ分析
  - Stripe: 財務分析
  - Uber: 地理空間データ処理
  - Meta: データサイエンスワークフロー

統合ツール:
  - dbt: ネイティブサポート
  - Apache Superset: コネクタ提供
  - Metabase: ネイティブドライバー
  - Streamlit: 直接統合

コミュニティの拡大

2025年のマイルストーン:
- DuckCon 2025: 初の大規模カンファレンス開催
- エクステンションエコシステム: 50+ 公式/コミュニティ拡張
- 日本語ドキュメント: コミュニティ翻訳プロジェクト完了
- 教育機関: 大学のデータサイエンスコースで採用増加

拡張機能(Extensions)

import duckdb

# 利用可能な拡張機能を確認
duckdb.query("SELECT * FROM duckdb_extensions()").df()

# 拡張機能のインストールと読み込み
duckdb.install_extension("spatial")
duckdb.load_extension("spatial")

# 空間データ分析
result = duckdb.query("""
    SELECT
        ST_AsText(geometry) as wkt,
        ST_Area(geometry) as area
    FROM 'buildings.geojson'
    WHERE ST_Within(geometry, ST_GeomFromText('POLYGON(...)'))
""").df()

# その他の主要拡張機能
# - httpfs: HTTP/S3/GCS からのファイル読み込み
# - json: 高度なJSON処理
# - parquet: Parquet最適化
# - sqlite_scanner: SQLiteファイルの読み込み
# - postgres_scanner: PostgreSQLへの直接接続

パフォーマンス最適化

ベストプラクティス

import duckdb

# 1. 並列処理の設定
conn = duckdb.connect()
conn.execute("SET threads TO 8")  # CPUコア数に応じて設定
conn.execute("SET memory_limit = '8GB'")  # メモリ制限

# 2. Parquet形式の活用
conn.execute("""
    COPY (SELECT * FROM large_csv.csv)
    TO 'optimized.parquet' (
        FORMAT PARQUET,
        COMPRESSION ZSTD,
        ROW_GROUP_SIZE 100000
    )
""")

# 3. パーティショニング
conn.execute("""
    COPY (SELECT * FROM sales WHERE year = 2025)
    TO 'sales' (
        FORMAT PARQUET,
        PARTITION_BY (month, region),
        OVERWRITE_OR_IGNORE
    )
""")

# 4. インデックスの活用(永続データベース)
conn.execute("""
    CREATE INDEX idx_customer ON orders(customer_id)
""")

クエリ最適化

-- EXPLAIN ANALYZEでクエリプランを確認
EXPLAIN ANALYZE
SELECT
    customer_id,
    SUM(amount) as total
FROM orders
WHERE order_date >= '2025-01-01'
GROUP BY customer_id;

-- フィルタプッシュダウンの活用
-- Parquetの列プルーニングが自動適用される
SELECT customer_id, amount
FROM 'orders.parquet'
WHERE status = 'completed';  -- フィルタがスキャン時に適用

まとめ

DuckDBは2025年、ローカル分析のデファクトスタンダードとして確立しました。組み込み型OLAPの特性を活かし、以下のような変革をもたらしています:

技術的優位性:

  • 列指向ストレージによる高速分析
  • pandas/Polarsとのシームレスな統合
  • Parquet/CSV直接クエリ
  • ゼロセットアップの利便性

実用的メリット:

  • クラウドコストの削減
  • データプライバシーの確保
  • 開発サイクルの高速化
  • 学習コストの低さ

エコシステムの成熟:

  • MotherDuckによるクラウド拡張
  • 豊富な拡張機能
  • 主要ツールとの統合

データ分析の第一歩として、まずはDuckDBでローカル分析を始め、必要に応じてクラウドDWHと組み合わせるハイブリッドアプローチが2025年のベストプラクティスです。

参考: DuckDB公式ドキュメント

この技術を体系的に学びたいですか?

未来学では東証プライム上場企業のITエンジニアが24時間サポート。月額24,800円から、退会金0円のオンラインIT塾です。

LINEで無料相談する
← 一覧に戻る