OLTP + OLAP デュアル構成
ここまでで、列指向ストレージの仕組み、ClickHouse の基本、データ投入パターン を学びました。
このドキュメントでは、OLTP データベース(PostgreSQL 等)と OLAP データベース(ClickHouse 等)を組み合わせた設計パターンを学びます。
idp-server での適用例: このデュアル構成を idp-server のテナント統計機能に適用した場合の詳細は テナント統計機能 実装ガイド を参照してください。
なぜデュアル構成か
1つのデータベースで全てをまかなうと、どこかで限界が来ます。
┌─────────────────────────────────────────────────────────────────┐
│ OLTP DB 1台で全部やる場合 │
│ │
│ 通常のリクエスト処理 年次レポート生成 │
│ (INSERT + SELECT) (数億行 GROUP BY) │
│ │ │ │
│ └──────┬───────────────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ PostgreSQL │ │
│ │ │ ← 通常処理が遅くなる(レポートがIO占有)│
│ │ │ ← レポートも遅い(行指向で非効率) │
│ └──────────────────┘ │
│ │
│ 「リクエスト処理とレポート生成が互いに足を引っ張る」 │
└────────────────────────────────────────────────────────────── ───┘
┌─────────────────────────────────────────────────────────────────┐
│ デュアル構成の場合 │
│ │
│ 通常のリクエスト処理 年次レポート生成 │
│ (INSERT + SELECT) (数億行 GROUP BY) │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ PostgreSQL │ │ ClickHouse │ │
│ │ (OLTP) │ │ (OLAP) │ │
│ │ │ │ │ │
│ │ ・リクエスト 数ms │ │ ・年次集計 数秒 │ │
│ │ ・トランザクション │ │ ・事前集計不要 │ │
│ │ ・更新/削除 │ │ ・圧縮10-40倍 │ │
│ └──────────┬────────┘ └──────────────────┘ │
│ │ CDC (自動同期) ↑ │
│ └──────────────────┘ │
│ │
│ 「それぞれ得意なことに専念」 │
└─────────────────────────────────────────────────────────────────┘
アーキテクチャ全体像
典型的なデュアル構成のアーキテクチャ:
┌─ アプリケーション ──────────────────────────────────────────────────┐
│ │
│ ┌─ OLTP パス(トランザクション処理)────────────────────────────┐ │
│ │ │ │
│ │ リクエスト処理 → ビジネスロジック → イベント記録 │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ PostgreSQL │ │ │
│ │ │ │ │ │
│ │ │ ・ユーザー/セッション/トランザクションデータ │ │ │
│ │ │ ・イベントログ (INSERT) │ │ │
│ │ │ ・マスターデータ │ │ │
│ │ └───────────────────────────────┬──────────────────── ──┘ │ │
│ │ │ │ │
│ └───────────────────────────────────┼───────────────────────────┘ │
│ │ │
│ │ CDC(イベントログのみ同期) │
│ │ │
│ ┌─ OLAP パス(分析・統計)──────────┼───────────────────────────┐ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ ClickHouse │ │ │
│ │ │ │ │ │
│ │ │ ・イベントログ(全量、列指向、圧縮) │ │ │
│ │ │ ・生データから直接 COUNT / GROUP BY / uniqExact │ │ │
│ │ │ ・事前集計テーブル不要 │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 統計API / ダッシュボード / レポート │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
何を同期するか
全テーブルを同期する必要はありません。分析対象のテーブルだけ。
| テーブルの種類 | 同期 | 理由 |
|---|---|---|
| イベントログ(大量、追記型) | ✅ | 分析の主対象。CDC で自動同期 |
| マスターデータ(ユーザー、設定等) | △ | JOIN が必要な場合のみ。ClickHouse の Dictionary で参照 |
| トランザクションデータ(セッション、トークン等) | ❌ | OLTP 専用。分析対象外 |
| 事前集計テーブル | ❌ | OLAP があれば不要(生データから直接集計) |
事前集計テーブルが不要になる理由
OLTP のみの構成では、大量データの集計に耐えられないため事前集計テーブルを用意します。
OLTP のみ:
events テーブル(数億行)→ 直接 COUNT は遅い
→ statistics テーブル(事前集計)を別途用意
→ バッチ処理で定期的に集計
→ ロック競合、TZ問題、テーブル設計の複雑化...
OLTP + OLAP:
events テーブル(数億行)→ ClickHouse にコピー
→ ClickHouse で直接 COUNT / GROUP BY(数秒)
→ 事前集計テーブル不要
→ バッチ処理不要
→ 統計周りのコード・テーブル・運用がまるごと消える
データフローの詳細
書き込みフロー
ユーザー → リクエスト → アプリケーション
│
▼
PostgreSQL
INSERT INTO events (...)
│
│ WAL (論理レプリケーション)
▼
CDC ツール (PeerDB 等)
変換 + バッチ化
│
▼
ClickHouse
INSERT INTO events (...)
(ReplacingMergeTree)
アプリは PostgreSQL にだけ書く。 ClickHouse への同期は CDC ツールが自動で行う。
読み取りフロー
┌─ 分析クエリのルーティング ──────────────────────────────┐
│ │
│ アプリケーション │
│ ┌──────────────────────────────────────────────────┐ │
│ │ │ │
│ │ if (olap が利用可能) { │ │
│ │ // ClickHouse: 生データから直接集計 │ │
│ │ clickHouseJdbc.query(""" │ │
│ │ SELECT toStartOfMonth(created_at) AS month, │ │
│ │ count(*) AS total, │ │
│ │ uniqExact(user_id) AS unique_users │ │
│ │ FROM events │ │
│ │ WHERE tenant_id = ? AND ... │ │
│ │ GROUP BY month │ │
│ │ """); │ │
│ │ } else { │ │
│ │ // PostgreSQL: 事前集計テーブルから取得 │ │
│ │ postgresJdbc.query(""" │ │
│ │ SELECT stat_date, metric, value │ │
│ │ FROM statistics │ │
│ │ WHERE tenant_id = ? AND ... │ │
│ │ """); │ │
│ │ } │ │
│ │ │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────── ──────────────────────┘
段階的な導入
一度に全部変える必要はありません。
Step 1: 検証(数分)
ClickHouse の PostgreSQL エンジンで、既存データを直接クエリしてみる。データ移動なし。
-- ClickHouse から PostgreSQL を直接参照
CREATE TABLE pg_events (...)
ENGINE = PostgreSQL('pg-host:5432', 'mydb', 'events', 'user', 'pass');
SELECT type, count(*) FROM pg_events GROUP BY type;
Step 2: アーカイブ分析
古いデータを S3 にエクスポートし、ClickHouse で読み込んで分析。
SELECT type, count(*)
FROM s3('s3://my-bucket/archive/**/*.parquet', ...)
GROUP BY type;
Step 3: CDC でリアルタイム同期
CDC ツールを導入し、イベントログをリアルタイム同期。分析APIの読み取り先を ClickHouse に切り替え。
Step 4: 事前集計テーブルの廃止
ClickHouse で全集計が賄えることを確認後、PostgreSQL 側の事前集計テーブルとバッチ処理を段階的に廃止。