LabHub

ブログ

ClickHouseリアルタイムOLAPとMergeTree最適化ガイド

한국어English日本語

ClickHouse OLAP

1. はじめに: OLTP vs OLAP、なぜ ClickHouse なのか

データベースを選ぶときに最初に見分けるべきなのは、ワークロードの性格だ。OLTP(Online Transaction Processing)は短いトランザクションと行単位の読み書きに最適化されており、OLAP(Online Analytical Processing)は数十億行にわたる集計クエリと分析に特化している。

特性OLTPOLAP
代表的なクエリSELECT * FROM users WHERE id = 42SELECT country, COUNT(*) FROM events GROUP BY country
データアクセスパターン行単位、ポイントルックアップカラム単位、フルスキャン/集計
同時実行性数千 TPS の短いトランザクション少数の重い分析クエリ
レイテンシ目標1~10ms100ms~数秒
代表的なエンジンPostgreSQL, MySQL, OracleClickHouse, BigQuery, Druid

PostgreSQL に pg_analytics 拡張を付けたり、TimescaleDB を活用して分析クエリを処理する方法もある。しかしデータが数十億行を超え、リアルタイムに近いダッシュボードを運用しなければならず、サブ秒(sub-second)レベルの応答が必要なら、専用の OLAP エンジンは必須だ。

ClickHouse は Yandex がウェブ分析サービス Metrica のために 2016 年にオープンソースとして公開したカラム指向の分析データベースだ。現在は ClickHouse Inc. が開発を主導しており、2025~2026 年にかけてベクトル検索、SharedCatalog、AI 統合などの革新的な機能が追加されている。単一サーバーでも毎秒数十億行をスキャンでき、クラスターに拡張すればペタバイト規模のデータをサブ秒のレイテンシで分析できる。

ClickHouse を選ぶべきシナリオは明確だ。

逆に 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 の基本動作

  1. INSERT: データがメモリバッファに溜まり、しきい値に達するとソート済みのパートとしてディスクに書き込まれる。
  2. Merge: バックグラウンドスレッドが小さなパートを定期的にマージして大きなパートを作る。マージの過程で重複除去や集計などの特殊なロジックが実行されることがある。
  3. SELECT: クエリ時に Primary Key(Sparse Index)を活用して必要なグラニュール(granule)だけを読む。デフォルトのグラニュールサイズは 8,192 行だ。

エンジンファミリーの比較

エンジン中核となる機能ユースケース
MergeTree基本エンジン、ORDER BY に基づくソート格納汎用の分析テーブル
ReplacingMergeTree同じ ORDER BY キーの重複行を最新バージョンで置き換えるCDC パイプライン、状態スナップショット
SummingMergeTreeマージ時に数値カラムを自動で合算カウンター、累積メトリクス
AggregatingMergeTreeマージ時に AggregateFunction の状態を自動でマージMaterialized View の事前集計
CollapsingMergeTreesign カラム(+1/-1)で行を挿入/取り消し変更ログに基づくリアルタイム集計
VersionedCollapsingMergeTreeCollapsing + バージョン管理順序が保証されない 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)としても機能し、データの物理的なソート順を決める。

中核となる原則:

-- 誤った設計: 高カーディナリティのカラムが前にある
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;

このパターンの中核となる利点は以下のとおりだ。

警告: 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 ワークロードでよく比較される四つのエンジンの特性を整理する。

項目ClickHousePostgreSQL (+ pg_analytics)BigQueryApache 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 になじみがある)高い (セグメントの概念)

中核となる選択基準:

8. 2025~2026 年の主要な新機能

ClickHouse は 2025 年の一年間で 50 以上のリリースを通じて大規模な機能アップデートを進めた。2026 年初頭までの主要な変化を整理する。

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 モデルをクエリパイプラインに直接つなぐこともできる。

その他の主要な改善

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 の運用上の注意点:

バックアップ戦略

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" エラーを引き起こす。

推奨事項:

-- 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 個だ。

復旧手順:

  1. INSERT のトラフィックを直ちに止めるか、バッチサイズを大きくする。
  2. バックグラウンドマージが追いつくまで待つ。system.merges テーブルで進行状況を監視する。
  3. 必要なら OPTIMIZE TABLE events FINAL を手動で実行する。(注意: このコマンドはディスク I/O が非常に大きいので、ピーク時間を避けなければならない。)
  4. 根本原因を解決する: 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.replicasis_session_expired または queue_size が異常に高い。

原因: ClickHouse Keeper の接続断、ネットワーク分断、あるいはレプリカのひとつが長期間ダウンしたあとに復旧して発生することがある。

復旧手順:

  1. system.replicas で状態を確認する。
  2. レプリケーションキューが正常に処理されているか確認する。
  3. キューが詰まっているなら SYSTEM RESTART REPLICA table_name を試す。
  4. 最悪の場合は問題のレプリカのデータを削除し、正常なレプリカから改めて複製する。
-- レプリカの状態を確認
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 の未設定、圧縮コーデックの未適用、あるいは想定より速いデータ増加。

復旧手順:

  1. 最も大きいパーティションを特定し、不要なデータを DROP PARTITION で直ちに削除する。
  2. 一時的なディスク領域を確保する(システムログ、一時ファイルの削除)。
  3. TTL ポリシーを追加して自動削除/移動を設定する。
  4. マルチディスクポリシーでコールドストレージ(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 には反映されないことがある。

復旧手順:

  1. MV の対象テーブルを TRUNCATE する。
  2. 元テーブルから手動でバックフィルする。
  3. 大規模なバックフィルでは日付範囲ごとに分けて実行し、メモリ使用を制御する。
-- 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 を本番にデプロイする前に確認すべき項目を整理する。

スキーマ設計

データ取り込み

クエリ性能

運用/安定性

監視指標

12. 参考資料

コメント

まだコメントはありません。

ログインするとコメントできます