- 1. はじめに: OLTP vs OLAP、なぜ ClickHouse なのか
- 2. ClickHouse アーキテクチャ: カラムストレージと圧縮
- 3. MergeTree エンジンファミリー
- 4. スキーマ設計のベストプラクティス
- 5. Materialized View と事前集計
- 6. クエリ最適化のテクニック
- 7. ClickHouse vs PostgreSQL vs BigQuery vs Druid
- 8. 2025~2026 年の主要な新機能
- 9. 運用時の注意点
- 10. 失敗事例と復旧手順
- 11. 本番チェックリスト
- 12. 参考資料

1. はじめに: OLTP vs OLAP、なぜ ClickHouse なのか
データベースを選ぶときに最初に見分けるべきなのは、ワークロードの性格だ。OLTP(Online Transaction Processing)は短いトランザクションと行単位の読み書きに最適化されており、OLAP(Online Analytical Processing)は数十億行にわたる集計クエリと分析に特化している。
| 特性 | OLTP | OLAP |
|---|---|---|
| 代表的なクエリ | SELECT * FROM users WHERE id = 42 | SELECT country, COUNT(*) FROM events GROUP BY country |
| データアクセスパターン | 行単位、ポイントルックアップ | カラム単位、フルスキャン/集計 |
| 同時実行性 | 数千 TPS の短いトランザクション | 少数の重い分析クエリ |
| レイテンシ目標 | 1~10ms | 100ms~数秒 |
| 代表的なエンジン | PostgreSQL, MySQL, Oracle | ClickHouse, BigQuery, Druid |
PostgreSQL に pg_analytics 拡張を付けたり、TimescaleDB を活用して分析クエリを処理する方法もある。しかしデータが数十億行を超え、リアルタイムに近いダッシュボードを運用しなければならず、サブ秒(sub-second)レベルの応答が必要なら、専用の OLAP エンジンは必須だ。
ClickHouse は Yandex がウェブ分析サービス Metrica のために 2016 年にオープンソースとして公開したカラム指向の分析データベースだ。現在は ClickHouse Inc. が開発を主導しており、2025~2026 年にかけてベクトル検索、SharedCatalog、AI 統合などの革新的な機能が追加されている。単一サーバーでも毎秒数十億行をスキャンでき、クラスターに拡張すればペタバイト規模のデータをサブ秒のレイテンシで分析できる。
ClickHouse を選ぶべきシナリオは明確だ。
- イベント/ログ/メトリクスデータが日単位で数億件以上蓄積される場合
- ダッシュボードが数百個の GROUP BY 集計をサブ秒で返さなければならない場合
- INSERT はバッチ単位で多く、UPDATE/DELETE はほとんどない append-heavy なワークロード
- カラム単位の圧縮でストレージコストを劇的に下げたい場合
逆に ClickHouse が適さない場合もある。行単位のポイントルックアップが主なワークロードである場合、ACID トランザクションが必須である場合、頻繁な UPDATE/DELETE が必要な場合には、PostgreSQL や MySQL のほうが適している。
2. ClickHouse アーキテクチャ: カラムストレージと圧縮
カラム指向ストレージの原理
伝統的な行指向(row-oriented)データベースは、一つの行に属するすべてのカラム値を連続したブロックに格納する。SELECT country FROM events のように単一カラムだけが必要なクエリでも、残りすべてのカラムデータをディスクから読まなければならないので I/O の無駄が大きい。
ClickHouse は各カラムの値を別々のファイルに連続して格納する。分析クエリが必要とするカラムだけを読むので、ディスク I/O が劇的に減る。同じ型の値が連続して並ぶため、圧縮率もはるかに高い。
行指向 (PostgreSQL):
┌──────┬─────────┬─────────┬──────────┐
│ id │ country │ browser │ duration │ ← 行 1
├──────┼─────────┼─────────┼──────────┤
│ id │ country │ browser │ duration │ ← 行 2
└──────┴─────────┴─────────┴──────────┘
カラム指向 (ClickHouse):
┌──────────────────────┐
│ id: 1, 2, 3, 4, ... │ ← カラムファイル 1
├──────────────────────┤
│ country: KR, US, ... │ ← カラムファイル 2
├──────────────────────┤
│ browser: Chrome, ... │ ← カラムファイル 3
├──────────────────────┤
│ duration: 42, 17, ...│ ← カラムファイル 4
└──────────────────────┘
圧縮アルゴリズム: LZ4 vs ZSTD
ClickHouse はデフォルトで LZ4 圧縮を使う。LZ4 は圧縮率こそ中程度だが、圧縮/解凍の速度が非常に速い。より高い圧縮率が必要なら ZSTD を選べる。
| アルゴリズム | 圧縮率 | 圧縮速度 | 解凍速度 | 推奨シナリオ |
|---|---|---|---|---|
| LZ4 | 普通 (3~5x) | 非常に速い | 非常に速い | リアルタイムクエリ中心、ホットデータ |
| ZSTD | 高い (5~10x) | 速い | 速い | コールドデータ、ストレージ節約が優先 |
| Delta + ZSTD | 非常に高い | 普通 | 普通 | タイムスタンプ、連続して増加する整数 |
| DoubleDelta + LZ4 | 高い | 速い | 速い | メトリクスデータ(ほぼ一定の間隔) |
カラムごとにコーデックを指定できる点が ClickHouse の強力な長所だ。
CREATE TABLE events
(
event_time DateTime CODEC(DoubleDelta, LZ4),
user_id UInt64 CODEC(Delta, ZSTD(3)),
country LowCardinality(String) CODEC(ZSTD(1)),
duration Float32 CODEC(Gorilla, LZ4),
raw_json String CODEC(ZSTD(5))
)
ENGINE = MergeTree()
ORDER BY (event_time, user_id);
event_time のように単調増加するタイムスタンプには DoubleDelta コーデックが効果的で、duration のような浮動小数点のメトリクスには Gorilla コーデックが適している。頻繁にアクセスしない大きな JSON フィールドには ZSTD のレベルを上げてストレージを節約する。
ベクトル化クエリ実行
ClickHouse は一度に一行ずつではなく、数千個の値をまとめたカラムベクトル単位で演算を行う。これによって CPU キャッシュ効率が最大化され、SIMD 命令(SSE4.2, AVX2, AVX-512)を活用した並列処理が可能になる。単一コアでも毎秒数億行を処理できる理由は、まさにこのベクトル化実行エンジンにある。
3. MergeTree エンジンファミリー
MergeTree は ClickHouse の中核となるテーブルエンジンだ。名前のとおりデータをパート(part)単位で格納し、バックグラウンドでパートをマージ(merge)する LSM-Tree に似た構造に従う。
MergeTree の基本動作
- INSERT: データがメモリバッファに溜まり、しきい値に達するとソート済みのパートとしてディスクに書き込まれる。
- Merge: バックグラウンドスレッドが小さなパートを定期的にマージして大きなパートを作る。マージの過程で重複除去や集計などの特殊なロジックが実行されることがある。
- SELECT: クエリ時に Primary Key(Sparse Index)を活用して必要なグラニュール(granule)だけを読む。デフォルトのグラニュールサイズは 8,192 行だ。
エンジンファミリーの比較
| エンジン | 中核となる機能 | ユースケース |
|---|---|---|
| MergeTree | 基本エンジン、ORDER BY に基づくソート格納 | 汎用の分析テーブル |
| ReplacingMergeTree | 同じ ORDER BY キーの重複行を最新バージョンで置き換える | CDC パイプライン、状態スナップショット |
| SummingMergeTree | マージ時に数値カラムを自動で合算 | カウンター、累積メトリクス |
| AggregatingMergeTree | マージ時に AggregateFunction の状態を自動でマージ | Materialized View の事前集計 |
| CollapsingMergeTree | sign カラム(+1/-1)で行を挿入/取り消し | 変更ログに基づくリアルタイム集計 |
| VersionedCollapsingMergeTree | Collapsing + バージョン管理 | 順序が保証されない CDC ストリーム |
ReplacingMergeTree の例
CDC(Change Data Capture)パイプラインで同じキーに対する複数バージョンの行が入ってくるとき、最新の状態だけを保ちたいなら ReplacingMergeTree を使う。
CREATE TABLE user_profiles
(
user_id UInt64,
name String,
email String,
updated_at DateTime
)
ENGINE = ReplacingMergeTree(updated_at)
ORDER BY user_id;
-- 同じ user_id で複数回 INSERT できる
INSERT INTO user_profiles VALUES (1, 'Kim Youngju', 'yj@example.com', '2026-03-01 10:00:00');
INSERT INTO user_profiles VALUES (1, 'Kim Youngju', 'yj_new@example.com', '2026-03-06 09:00:00');
-- マージ前は両方の行が見えることがある
-- FINAL キーワードで最新バージョンだけを取得
SELECT * FROM user_profiles FINAL WHERE user_id = 1;
注意: FINAL キーワードはクエリ時点で重複除去を行うため、性能上のオーバーヘッドがある。本番では OPTIMIZE TABLE ... FINAL を定期的に実行するか、クエリレベルで argMax 関数を活用するほうが効率的な場合が多い。
4. スキーマ設計のベストプラクティス
ORDER BY 設計の原則
ORDER BY は ClickHouse で最も重要な設計上の決定だ。Primary Key(Sparse Index)としても機能し、データの物理的なソート順を決める。
中核となる原則:
- 最も頻繁にフィルタするカラムを前に置く。
- カーディナリティが低いカラムを前に、高いカラムを後ろに置く。
- 時系列データでは
(low_cardinality_dim, timestamp)の順が一般的だ。
-- 誤った設計: 高カーディナリティのカラムが前にある
CREATE TABLE events_bad
(
event_id UUID,
user_id UInt64,
event_type LowCardinality(String),
event_time DateTime
)
ENGINE = MergeTree()
ORDER BY (event_id); -- UUID が最初: sparse index の効率が極めて低い
-- 正しい設計: フィルタパターンに合わせたソート
CREATE TABLE events_good
(
event_id UUID,
user_id UInt64,
event_type LowCardinality(String),
event_time DateTime
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_type, user_id, event_time);
パーティショニング戦略
PARTITION BY はデータを物理的に分離して、古いデータの削除(TTL または DROP PARTITION)を効率的にし、クエリ時のパーティションプルーニングを可能にする。
| パーティションキー | パーティション数(1 年基準) | 推奨シナリオ |
|---|---|---|
toYYYYMM(event_time) | 12 個 | 月単位の保持ポリシー、大半の時系列 |
toYYYYMMDD(event_time) | 365 個 | 日単位の TTL が必要なログ |
toMonday(event_time) | 52 個 | 週単位の分析が主なパターン |
intDiv(user_id, 1000000) | 可変 | ユーザー ID の範囲に基づく検索 |
警告: パーティション数が過度に多くなると(数千個以上)、ZooKeeper/ClickHouse Keeper のメタデータ負荷が急増し、パート管理のオーバーヘッドが大きくなる。月単位のパーティショニングが最も無難だ。
LowCardinality 型
カーディナリティが数千以下の文字列カラムには必ず LowCardinality(String) を使う。内部的に辞書エンコーディングを適用して保存領域を減らし、クエリ性能を高める。
-- LowCardinality 適用前後の比較
CREATE TABLE logs_v1 (level String) ENGINE = MergeTree() ORDER BY tuple();
CREATE TABLE logs_v2 (level LowCardinality(String)) ENGINE = MergeTree() ORDER BY tuple();
-- データ挿入後のサイズ比較
SELECT
table,
formatReadableSize(sum(data_compressed_bytes)) AS compressed,
formatReadableSize(sum(data_uncompressed_bytes)) AS uncompressed
FROM system.columns
WHERE database = currentDatabase() AND table IN ('logs_v1', 'logs_v2')
GROUP BY table;
カーディナリティが 10,000 を超えると LowCardinality の利点が減り、むしろオーバーヘッドになりうるので、適用前に SELECT uniq(column) FROM table で確認する。
5. Materialized View と事前集計
Materialized View(MV)は ClickHouse でリアルタイムの事前集計を実現する中核メカニズムだ。PostgreSQL の Materialized View とは根本的に異なる。ClickHouse の MV は INSERT トリガーのように動作し、元テーブルにデータが挿入されるたびに、変換/集計された結果を自動で対象テーブルに書き込む。
AggregatingMergeTree + State/Merge パターン
最も強力な事前集計パターンは、AggregatingMergeTree エンジンと -State/-Merge コンビネータを組み合わせることだ。
-- ステップ 1: 元のイベントテーブル
CREATE TABLE raw_events
(
event_time DateTime,
event_type LowCardinality(String),
user_id UInt64,
revenue Float64
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_type, event_time);
-- ステップ 2: 事前集計の対象テーブル (AggregatingMergeTree)
CREATE TABLE hourly_stats
(
hour DateTime,
event_type LowCardinality(String),
user_count AggregateFunction(uniq, UInt64),
total_rev AggregateFunction(sum, Float64),
p99_rev AggregateFunction(quantile(0.99), Float64)
)
ENGINE = AggregatingMergeTree()
PARTITION BY toYYYYMM(hour)
ORDER BY (event_type, hour);
-- ステップ 3: Materialized View の作成
CREATE MATERIALIZED VIEW hourly_stats_mv
TO hourly_stats
AS SELECT
toStartOfHour(event_time) AS hour,
event_type,
uniqState(user_id) AS user_count,
sumState(revenue) AS total_rev,
quantileState(0.99)(revenue) AS p99_rev
FROM raw_events
GROUP BY hour, event_type;
-- ステップ 4: 取得時に -Merge コンビネータを使う
SELECT
hour,
event_type,
uniqMerge(user_count) AS unique_users,
sumMerge(total_rev) AS total_revenue,
quantileMerge(0.99)(p99_rev) AS revenue_p99
FROM hourly_stats
WHERE hour >= '2026-03-01'
GROUP BY hour, event_type
ORDER BY hour;
このパターンの中核となる利点は以下のとおりだ。
- リアルタイム性: INSERT が発生するたびに即座に集計が更新される。
- 正確性:
-State/-Merge関数は中間状態をバイナリで保存するため、パートのマージ後も数学的に正確な結果を保証する。 - クエリ速度: 数十億行の元データではなく集計テーブルを参照するので、応答がミリ秒単位まで下がる。
- 近似値のサポート:
uniq(HyperLogLog ベース)、quantile(t-digest ベース)などの近似集計関数も State/Merge パターンと完全に互換だ。
警告: Materialized View は INSERT の時点でしかトリガーされない。既存のデータに MV を遡って適用するには、INSERT INTO hourly_stats SELECT ... FROM raw_events で手動のバックフィルが必要だ。
6. クエリ最適化のテクニック
PREWHERE: WHERE より先にフィルタする
ClickHouse 固有の PREWHERE 句は、WHERE 条件の一部をカラムの読み込み前に評価し、不要なカラムデータのディスク I/O を減らす。最新バージョンではオプティマイザが自動的に WHERE を PREWHERE に変換するが、明示的に指定することもできる。
-- PREWHERE を明示的に使う
SELECT user_id, raw_json
FROM raw_events
PREWHERE event_type = 'purchase'
WHERE revenue > 100.0;
上のクエリでは event_type カラムだけを先に読んでフィルタし、通過した行に対してのみ raw_json(大きな String カラム)を読む。大きなカラムを含むテーブルでは PREWHERE の効果は劇的だ。
サンプリングでおおよその結果を素早く得る
データ探索の段階では、正確な集計より速いフィードバックのほうが重要なことがある。SAMPLE 句を使えば、データの一部だけを読んで近似結果を返す。
-- テーブル作成時に SAMPLE BY の指定が必要
CREATE TABLE events_sampled
(
event_time DateTime,
user_id UInt64,
event_type LowCardinality(String),
revenue Float64
)
ENGINE = MergeTree()
ORDER BY (event_type, sipHash64(user_id))
SAMPLE BY sipHash64(user_id);
-- 全データの 10% だけをサンプリングして取得
SELECT
event_type,
count() * 10 AS estimated_count, -- 10 倍で補正
avg(revenue) AS avg_revenue
FROM events_sampled
SAMPLE 0.1
GROUP BY event_type;
並列実行とリソース制御
ClickHouse はデフォルトで利用可能なすべての CPU コアを使ってクエリを並列実行する。本番環境では、クエリ間のリソース競合を防ぐために適切な制限が必要だ。
-- クエリレベルのリソース制限
SET max_threads = 8; -- クエリあたりの最大スレッド数
SET max_memory_usage = 10000000000; -- クエリあたりの最大メモリ (10GB)
SET max_execution_time = 30; -- 最大実行時間 (秒)
SET max_rows_to_read = 1000000000; -- 最大読み取り行数
-- ユーザー/プロファイル単位の制限 (config.xml または SQL)
CREATE SETTINGS PROFILE 'analyst' SETTINGS
max_threads = 4,
max_memory_usage = 5000000000,
max_execution_time = 60
TO analyst_role;
クエリ性能の分析
遅いクエリを診断するときは clickhouse-client のプロファイリングオプションを活用する。
# クエリ実行統計を含めて実行
clickhouse-client --query "
SELECT event_type, count()
FROM raw_events
WHERE event_time >= '2026-03-01'
GROUP BY event_type
FORMAT PrettyCompactMonoBlock
SETTINGS send_logs_level = 'trace'
"
# system.query_log から遅いクエリを分析
clickhouse-client --query "
SELECT
query_duration_ms,
read_rows,
formatReadableSize(read_bytes) AS read_size,
formatReadableSize(memory_usage) AS peak_memory,
query
FROM system.query_log
WHERE type = 'QueryFinish'
AND query_duration_ms > 1000
ORDER BY query_duration_ms DESC
LIMIT 10
"
7. ClickHouse vs PostgreSQL vs BigQuery vs Druid
OLAP ワークロードでよく比較される四つのエンジンの特性を整理する。
| 項目 | ClickHouse | PostgreSQL (+ pg_analytics) | BigQuery | Apache Druid |
|---|---|---|---|---|
| ストレージモデル | カラム指向 | 行指向 (拡張でカラム対応) | カラム指向 (Capacitor) | カラム指向 (セグメント) |
| デプロイモデル | セルフホスティング / ClickHouse Cloud | セルフホスティング / マネージド | フルマネージドの SaaS | セルフホスティング / Imply Cloud |
| リアルタイム取り込み | INSERT 直後に参照可能 | INSERT 直後に参照可能 | ストリーミングバッファ (数秒の遅延) | リアルタイム取り込みノード (数秒の遅延) |
| クエリレイテンシ | ミリ秒~秒 | 秒~分 (大規模集計) | 秒~十秒 (コールドスタート) | ミリ秒~秒 |
| 圧縮率 | 非常に高い (10~40x) | 普通 (2~4x) | 高い (マネージド) | 高い (5~10x) |
| SQL 互換性 | ClickHouse SQL (標準互換性が高い) | 標準 SQL を完全サポート | 標準 SQL (GoogleSQL) | Druid SQL (限定的) |
| JOIN 性能 | 限定的 (大きな JOIN に注意) | 優秀 (ハッシュ、マージ、ネステッドループ) | 優秀 (分散シャッフル) | 非対応 (ルックアップ JOIN のみ) |
| UPDATE/DELETE | 非同期 Mutation (重い) | 即時処理 (MVCC) | DML 対応 (コストが発生) | 非対応 |
| コストモデル | インフラコスト (予測可能) | インフラコスト (予測可能) | スキャンバイト課金 (予測が難しい) | インフラコスト (予測可能) |
| 学習曲線 | 中程度 | 低い | 低い (SQL になじみがある) | 高い (セグメントの概念) |
中核となる選択基準:
- すでに PostgreSQL を使っていてデータが数十 GB 以下: PostgreSQL にとどまること。pg_analytics 拡張で十分な場合がある。
- 数十 TB 以上、自前インフラ、サブ秒の応答が必要: ClickHouse が最適だ。
- インフラ管理をしたくなく、コストに柔軟: BigQuery が適している。
- 高性能なリアルタイムダッシュボードが中心で JOIN が不要: Druid も選択肢だ。
8. 2025~2026 年の主要な新機能
ClickHouse は 2025 年の一年間で 50 以上のリリースを通じて大規模な機能アップデートを進めた。2026 年初頭までの主要な変化を整理する。
ベクトル検索 (Vector Search)
ClickHouse 25.1 から usearch インデックスを活用した ANN(Approximate Nearest Neighbor)検索が正式にサポートされる。別途のベクトル DB がなくても、分析データと埋め込みベクトルを同じテーブルで管理できる。
CREATE TABLE embeddings
(
doc_id UInt64,
content String,
vector Array(Float32),
INDEX vec_idx vector TYPE usearch(256) GRANULARITY 1
)
ENGINE = MergeTree()
ORDER BY doc_id;
-- コサイン類似度に基づく検索
SELECT doc_id, content,
cosineDistance(vector, [0.1, 0.2, ...]) AS distance
FROM embeddings
ORDER BY distance ASC
LIMIT 10;
SharedCatalog とオブジェクトストレージの分離
ClickHouse Cloud で導入された SharedCatalog は、コンピューティングとストレージを完全に分離し、複数のコンピューティングノードが S3/GCS の同じデータにアクセスできるようにする。これによって読み取り専用レプリカを即座にスケールアウトできる。
AI 統合とクエリ生成
ClickHouse は LLM ベースの自然言語-to-SQL 変換、AI ベースのクエリ最適化提案などの機能を ClickHouse Cloud コンソールに統合している。また UDF(User Defined Function)を通じて外部の ML モデルをクエリパイプラインに直接つなぐこともできる。
その他の主要な改善
- Parallel Replicas: 単一のクエリを複数のレプリカに分散実行してレイテンシを減らす機能が GA になった。
- Lightweight DELETE/UPDATE: Mutation の代わりに行マスキングに基づく軽量削除が安定化した。
- Refreshable Materialized View: 定期的に全体をリフレッシュする MV のサポートが追加された (既存の INSERT トリガー方式とは別)。
9. 運用時の注意点
ディスク管理
ClickHouse はバックグラウンドマージの過程で一時的に追加のディスク領域を使う。本番では最低 30% の空きディスク領域を確保しなければならず、ディスク使用率の監視は必須だ。
# /etc/clickhouse-server/config.d/storage.yaml
# マルチディスク(ホット/コールド)ストレージポリシー
storage_configuration:
disks:
hot:
type: local
path: /data/clickhouse/hot/
cold:
type: s3
endpoint: https://s3.ap-northeast-2.amazonaws.com/my-bucket/clickhouse/
access_key_id: '${S3_ACCESS_KEY}'
secret_access_key: '${S3_SECRET_KEY}'
policies:
tiered:
volumes:
hot_volume:
disk: hot
max_data_part_size_bytes: 10737418240 # 10GB
cold_volume:
disk: cold
move_factor: 0.8 # ホットディスクが 80% 埋まったらコールドへ移動
TTL(Time to Live)を活用すれば、古いデータを自動でコールドストレージに移したり削除したりできる。
ALTER TABLE raw_events
MODIFY TTL
event_time + INTERVAL 30 DAY TO VOLUME 'cold_volume',
event_time + INTERVAL 365 DAY DELETE;
レプリケーションと高可用性
本番では必ず ReplicatedMergeTree エンジンを使う。ClickHouse Keeper(ZooKeeper 互換の合意プロトコル)がレプリカ間のメタデータ同期を担当する。
-- Replicated テーブルの作成
CREATE TABLE raw_events ON CLUSTER 'production'
(
event_time DateTime,
event_type LowCardinality(String),
user_id UInt64,
revenue Float64
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/raw_events', '{replica}')
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_type, event_time);
ClickHouse Keeper の運用上の注意点:
- 必ず奇数ノード(3 または 5)で構成する。3 ノード構成では 1 ノードの障害まで許容する。
- Keeper ノードには専用の SSD を割り当てる。ディスクの遅延が合意プロトコルの性能に直接影響する。
raft_logs_levelを適切に設定してデバッグできるようにする。- Keeper のログとスナップショットの保管期間を設定してディスクの膨張を防ぐ。
バックアップ戦略
ClickHouse 内蔵のバックアップ機能を活用するか、clickhouse-backup ツールを使う。
# 内蔵の BACKUP コマンド (ClickHouse 22.8+)
clickhouse-client --query "
BACKUP TABLE raw_events
TO S3('https://s3.ap-northeast-2.amazonaws.com/my-backup-bucket/raw_events_20260306/',
'${S3_ACCESS_KEY}', '${S3_SECRET_KEY}')
SETTINGS compression_method = 'lz4'
"
# リストア
clickhouse-client --query "
RESTORE TABLE raw_events
FROM S3('https://s3.ap-northeast-2.amazonaws.com/my-backup-bucket/raw_events_20260306/',
'${S3_ACCESS_KEY}', '${S3_SECRET_KEY}')
"
# clickhouse-backup ツールの利用 (より細かい制御)
clickhouse-backup create --tables="default.raw_events" daily_20260306
clickhouse-backup upload daily_20260306
clickhouse-backup list remote
INSERT バッチの最適化
ClickHouse にデータを挿入するときに最もありがちなミスは、行単位で INSERT を送ることだ。INSERT のたびに新しいパートが作られ、過度なパート数は "Too many parts" エラーを引き起こす。
推奨事項:
- 最低 1,000 行以上を一度にバッチ INSERT する。理想的には 10,000~100,000 行だ。
- Kafka や RabbitMQ などから消費するときは、ClickHouse のテーブルエンジン(Kafka engine)を活用して自動バッチを構成する。
async_insert=1設定を活用すると、ClickHouse がクライアントの小さな INSERT をサーバー側でまとめてバッチ処理する。
-- async_insert を有効化
SET async_insert = 1;
SET wait_for_async_insert = 1; -- INSERT の応答前に実際の書き込みを待つ
SET async_insert_max_data_size = 10485760; -- 10MB ごとにフラッシュ
SET async_insert_busy_timeout_ms = 1000; -- 最大 1 秒待機
10. 失敗事例と復旧手順
事例 1: "Too many parts" エラー
症状: INSERT が DB::Exception: Too many parts (N). Merges are processing significantly slower than inserts エラーとともに失敗する。
原因: 行単位の INSERT を毎秒数千回実行してパート数が急増した。デフォルトのしきい値はパーティションあたり 300 個だ。
復旧手順:
- INSERT のトラフィックを直ちに止めるか、バッチサイズを大きくする。
- バックグラウンドマージが追いつくまで待つ。
system.mergesテーブルで進行状況を監視する。 - 必要なら
OPTIMIZE TABLE events FINALを手動で実行する。(注意: このコマンドはディスク I/O が非常に大きいので、ピーク時間を避けなければならない。) - 根本原因を解決する: async_insert の有効化、バッチサイズの増加、Kafka エンジンの導入など。
-- パーティションごとのパート数を確認
SELECT
partition,
count() AS part_count,
formatReadableSize(sum(bytes_on_disk)) AS total_size
FROM system.parts
WHERE table = 'raw_events' AND active
GROUP BY partition
ORDER BY part_count DESC;
-- マージの進行状況を監視
SELECT
table, partition_id,
progress, elapsed,
formatReadableSize(total_size_bytes_compressed) AS size
FROM system.merges
WHERE table = 'raw_events';
事例 2: レプリカの不一致 (Replica Divergence)
症状: 二つのレプリカのデータが食い違い、SELECT count() の結果が異なる。system.replicas で is_session_expired または queue_size が異常に高い。
原因: ClickHouse Keeper の接続断、ネットワーク分断、あるいはレプリカのひとつが長期間ダウンしたあとに復旧して発生することがある。
復旧手順:
system.replicasで状態を確認する。- レプリケーションキューが正常に処理されているか確認する。
- キューが詰まっているなら
SYSTEM RESTART REPLICA table_nameを試す。 - 最悪の場合は問題のレプリカのデータを削除し、正常なレプリカから改めて複製する。
-- レプリカの状態を確認
SELECT
database, table,
is_leader, is_readonly, is_session_expired,
future_parts, parts_to_check,
queue_size, inserts_in_queue, merges_in_queue,
log_pointer, total_replicas, active_replicas
FROM system.replicas
WHERE table = 'raw_events';
-- レプリケーションの再起動を試す
SYSTEM RESTART REPLICA raw_events;
-- それでもだめならレプリカを正常な複製元から再同期
-- (注意: 該当レプリカのローカルデータが削除される)
SYSTEM RESTORE REPLICA raw_events;
事例 3: ディスクフルによるサービス停止
症状: バックグラウンドマージの失敗、INSERT の失敗、極端な場合はサーバーの起動不能。
原因: TTL の未設定、圧縮コーデックの未適用、あるいは想定より速いデータ増加。
復旧手順:
- 最も大きいパーティションを特定し、不要なデータを
DROP PARTITIONで直ちに削除する。 - 一時的なディスク領域を確保する(システムログ、一時ファイルの削除)。
- TTL ポリシーを追加して自動削除/移動を設定する。
- マルチディスクポリシーでコールドストレージ(S3)を追加する。
-- パーティションごとのディスク使用量を確認
SELECT
partition,
formatReadableSize(sum(bytes_on_disk)) AS disk_usage,
min(min_time) AS oldest_data,
max(max_time) AS newest_data,
count() AS part_count
FROM system.parts
WHERE table = 'raw_events' AND active
GROUP BY partition
ORDER BY sum(bytes_on_disk) DESC;
-- 古いパーティションを直ちに削除 (不可逆!)
ALTER TABLE raw_events DROP PARTITION '202501';
事例 4: Materialized View のデータ不一致
症状: 元テーブルと MV の集計結果が合わない。
原因: MV の作成前にすでに存在していたデータは MV に反映されない。また INSERT の途中でサーバー障害が起きると、元テーブルには書き込まれたのに MV には反映されないことがある。
復旧手順:
- MV の対象テーブルを TRUNCATE する。
- 元テーブルから手動でバックフィルする。
- 大規模なバックフィルでは日付範囲ごとに分けて実行し、メモリ使用を制御する。
-- MV の対象テーブルを初期化してからバックフィル
TRUNCATE TABLE hourly_stats;
INSERT INTO hourly_stats
SELECT
toStartOfHour(event_time) AS hour,
event_type,
uniqState(user_id) AS user_count,
sumState(revenue) AS total_rev,
quantileState(0.99)(revenue) AS p99_rev
FROM raw_events
WHERE event_time >= '2026-01-01' AND event_time < '2026-02-01'
GROUP BY hour, event_type;
-- 月ごとに繰り返し実行
11. 本番チェックリスト
ClickHouse を本番にデプロイする前に確認すべき項目を整理する。
スキーマ設計
- ORDER BY キーが主要なクエリパターンと一致しているか
- PARTITION BY が適切な単位(月/週)で設定されているか (パーティション数 1,000 個未満)
- 低カーディナリティの文字列に
LowCardinality型が適用されているか - カラムごとに最適な圧縮コーデックが指定されているか
- Nullable の使用を最小化したか (Nullable は追加のカラムファイルを作る)
データ取り込み
- INSERT のバッチサイズが最低 1,000 行以上か
- async_insert が適切に設定されているか (小規模な INSERT が頻繁な場合)
- Kafka/RabbitMQ 連携でテーブルエンジンを活用しているか
- "Too many parts" のアラートが設定されているか
クエリ性能
- ダッシュボードのクエリに Materialized View の事前集計が適用されているか
- ユーザー/ロールごとのリソース制限(max_threads, max_memory_usage)が設定されているか
- system.query_log に基づくスロークエリ監視が構築されているか
- 大きな JOIN の代わりにディクショナリまたは IN サブクエリを使っているか
運用/安定性
- ReplicatedMergeTree で最低 2 レプリカを構成したか
- ClickHouse Keeper が 3+ の奇数ノードで運用されているか
- ディスク使用率 80% のアラートが設定されているか
- TTL ポリシーでデータ保持期間が管理されているか
- バックアップが自動化されており、リストアテストを定期的に実施しているか
- ホット/コールドストレージのティアリングが構成されているか (大容量データの場合)
監視指標
-
system.metricsでBackgroundMergesAndMutationsPoolTaskを監視 -
system.asynchronous_metricsでMaxPartCountForPartitionを監視 (300 を超えたら警告) -
system.replicasでqueue_size、is_readonlyを監視 - Prometheus + Grafana のダッシュボードで主要指標を可視化
12. 参考資料
- ClickHouse Academic Overview - アーキテクチャの詳細分析
- ClickHouse Query Optimisation - The Definitive Guide
- ClickHouse 2025 Roundup - 年間の主要機能まとめ
- ClickHouse vs PostgreSQL with Extensions - OLAP 性能比較
- Data Modeling Guide for Real-Time Analytics with ClickHouse
- ClickHouse 公式ドキュメント - MergeTree エンジンファミリー
- ClickHouse 公式ドキュメント - Materialized View