OLAP の本質 ─ 列指向・Bulk read という別世界
OLAP(Online Analytical Processing)は OLTP の双子のような対立軸として育ってきた。同じ「DB」と呼ばれるが、内部で起きていることは別世界。OLTP のセオリーを OLAP に持ち込むと破綻する。
この章では ClickHouse / DuckDB を主役に、OLAP の本質を見る。
OLAP の定義
OLAP: アナリスト向け、各クエリは大量のレコードをスキャンし、少数の列を読み、aggregation する。
第 3 章の 6 軸で言うと:
- read/write: read 圧倒
- レイテンシ: 秒〜分
- 整合性: Eventual(スナップショット同期で十分)
- データ形状: 列
- 寿命: Warm + Cold
- スケール: Sharding / 分散
OLTP が「多くの小さなトランザクション」だったのに対し、OLAP は「少数の大きなクエリ」。1 クエリで億単位の行をスキャンし、SUM / AVG / COUNT(DISTINCT) を返す。
なぜ列指向が速いのか
OLAP の典型クエリ:
SELECT
toStartOfMonth(created_at) AS month,
category,
SUM(amount) AS revenue
FROM orders
WHERE created_at > now() - INTERVAL 1 YEAR
GROUP BY month, category;
このクエリで触る列は created_at / category / amount の 3 つだけ。customer_name や shipping_address は読まない。
行指向ストレージだと:
ページ 1: [id=1, customer=Alice, address=..., created_at=2026-01-01, category=A, amount=100, ...]
ページ 2: [id=2, customer=Bob, address=..., created_at=2026-01-02, category=B, amount=200, ...]
各行を全カラム読み込まないと created_at を取れない。10 億行なら、不要なカラムも 10 億回読む。
列指向だと:
created_at 列: [2026-01-01, 2026-01-02, 2026-01-03, ...]
category 列: [A, B, A, B, A, ...]
amount 列: [100, 200, 150, 300, 80, ...]
customer 列: [Alice, Bob, Carol, ...] ← このクエリでは読まない
必要な列だけを連続的に読める。I/O が圧倒的に少ない。
graph TB
subgraph "行指向 OLTP(PostgreSQL)"
R1[Row 1: 全列]
R2[Row 2: 全列]
R3[Row 3: 全列]
end
subgraph "列指向 OLAP(ClickHouse / DuckDB)"
C1[id 列: 連続]
C2[created_at 列: 連続]
C3[amount 列: 連続]
end
R1 -.集計クエリは不利.-> X[全行・全列スキャン]
C1 -.集計クエリ.-> Y[必要列のみスキャン]
style X fill:#ffe1e1
style Y fill:#e1ffe1
圧縮効率の違い
列指向のもう一つの強みは 圧縮。同じ列の値は性質が似ているため、圧縮率が極めて高い。
category 列: [A, A, A, B, B, B, A, A, ...]
→ Run-Length Encoding: [(A, 3), (B, 3), (A, 2), ...]
created_at 列(タイムスタンプ): [t, t+1, t+5, t+8, ...]
→ Delta encoding: [t, +1, +4, +3, ...]
ClickHouse / Parquet / DuckDB は数十種類の圧縮アルゴリズムを使い分け、同じデータが行指向の 1/10 のサイズになることも珍しくない。これがそのまま I/O 削減 → クエリ速度に効く。
Vectorized execution
OLAP DB は vectorized execution(ベクトル化実行)を採用する。
OLTP の典型的な実行:行ごとに処理する(tuple-at-a-time)。
// 概念コード(OLTP 流)
for (row in rows) {
if (row.created_at > threshold) {
sum += row.amount;
}
}
OLAP は batch-at-a-time:
// 概念コード(OLAP 流、vectorized)
auto batch = read_column_batch("amount", 1024); // 1024 行を一気に
auto mask = batch > threshold; // SIMD で並列比較
sum += sum_with_mask(batch, mask); // SIMD で並列加算
CPU の SIMD 命令や cache line を最大限使えるため、行ごと処理に比べて 10-100 倍速い。DuckDB / ClickHouse / Snowflake / BigQuery などモダン OLAP は全部この路線。
ClickHouse の MergeTree
OLAP の代表的な実装として ClickHouse の MergeTree ストレージエンジンを見る。
基本構造
graph LR
A[INSERT バッチ] --> P1[Part 1<br/>sorted by ORDER BY]
A --> P2[Part 2<br/>sorted by ORDER BY]
A --> P3[Part 3<br/>sorted by ORDER BY]
P1 --> M[Merge<br/>バックグラウンド]
P2 --> M
P3 --> M
M --> P4[統合された Part]
style M fill:#fff4e1
- INSERT 単位で immutable な Part を作る
- バックグラウンドで Part を merge して大きくする
- 各 Part 内は ORDER BY で sort されている
- ORDER BY が primary sort key = sparse index の元
ORDER BY = primary key(PostgreSQL とは別物)
ClickHouse の PRIMARY KEY は uniqueness 制約ではない。データの並び順を決める。
CREATE TABLE events (
user_id UInt64,
event_type String,
timestamp DateTime,
data String
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(timestamp)
ORDER BY (user_id, event_type, timestamp);
ここで ORDER BY (user_id, event_type, timestamp) が設計の核心。データはこの順で物理的にソートされ、sparse index でクエリの skip scan が決まる。
Altinity の ClickHouse Knowledge Base のベストプラクティス:
- 3-5 カラム が適切
- 低カーディナリティを先頭に、高カーディナリティを後ろに
- 時系列データなら timestamp は最後 か
toStartOfDay(timestamp)を間に挟む
OLTP の B-tree インデックスとは設計の流儀が真逆。これを次章の「設計編」で深掘りする。
OLAP は Outside Data の世界
ここまで読んで気づくのは、OLAP DB は Pat Helland の “Outside Data” の住処だということ。
- Immutable: 一度書いたデータは原則変更しない(INSERT 中心、UPDATE/DELETE は重い)
- Identifier で参照: 「2026-05-09 の取引データ」と時間軸で識別される
- Stable: 一度確定したら結果が変わらない
OLTP(Inside Data)から ETL / CDC / Stream 経由で OLAP(Outside Data)へ流れる。これは第 12 章の Stream で再登場するテーマ。
graph LR
subgraph "Inside data: OLTP"
A[PostgreSQL]
end
subgraph "Outside data: OLAP"
B[ClickHouse]
C[DuckDB]
D[BigQuery]
end
A -- ETL / CDC / Stream --> B
A -- ETL / CDC / Stream --> C
A -- ETL / CDC / Stream --> D
style A fill:#e1f5ff
style B fill:#ffe1e1
style C fill:#ffe1e1
style D fill:#ffe1e1
OLAP DB を OLTP として使うとどうなるか
逆もまた真:
-- ClickHouse で 1 件 UPDATE
ALTER TABLE events UPDATE data = 'fixed' WHERE user_id = 123;
-- → 該当行を含む全 Part を書き換える非同期処理
-- → 数秒〜数分かかる、I/O も重い
OLAP は 更新を捨てている。代わりに大量スキャンと aggregation を最適化する。これは「ワークロードに合った DB を選ぶ」の典型例。
この章の要点
- OLAP = 大量レコードスキャン、少数列、aggregation 中心
- 列指向で I/O 削減、圧縮効率向上、vectorized execution
- ClickHouse の MergeTree は immutable Part + バックグラウンド merge + ORDER BY ソート
- ORDER BY が設計の核心。低 → 高 cardinality、3-5 列、時系列は最後
- OLAP は Outside data の世界。Immutable、ID 参照、Stable
次章への問いかけ
列指向の世界では「何の順で並べるか」が設計の起点になる。OLTP の正規化的な発想は通用しない。
次章で OLAP の設計編。Sort key の選び方・Dimensional Modeling・Materialized View、そして yuzutas0(風音屋)が説くアジャイルなデータモデリング。