メインコンテンツへスキップ

ClickHouse におけるパーツマージとは何ですか?


ClickHouse が高速なのは、クエリだけでなく insert でも同様です。これは、LSM trees に似た仕組みで動作するストレージ層によるものです。 MergeTree engine ファミリーのテーブルへの insert では、ソート済みで不変のデータパーツが作成されます。 ② すべてのデータ処理は、バックグラウンドでのパーツマージにオフロードされます。 その結果、データの書き込みは軽量で、非常に効率的になります。 テーブルごとのパーツ数を制御し、上記の ② を実現するために、ClickHouse はバックグラウンドで小さなパーツを大きなパーツへ継続的にマージし (パーティションごと) 、圧縮サイズがおよそ ~150 GB に達するまでこれを続けます。 次の図は、このバックグラウンドマージのプロセスを概略的に示したものです。
パーツの merge level は、マージのたびに 1 ずつ増加します。レベル 0 は、そのパーツが新規作成されたもので、まだマージされていないことを意味します。より大きなパーツにマージされたパーツはinactiveとしてマークされ、最終的には設定可能な時間の経過後に削除されます (デフォルトは 8 分) 。時間の経過とともに、これによりマージ済みパーツのツリーが形成されます。これが merge tree テーブルという名前の由来です。

マージの監視

テーブルパーツとはの例では、ClickHouse がすべてのテーブルパーツを parts システムテーブルで追跡していることを示しました。この例のテーブルにあるアクティブな各パーツについて、マージレベルと保存されている行数を取得するために、次のクエリを使用しました。
SELECT
    name,
    level,
    rows
FROM system.parts
WHERE (database = 'uk') AND (`table` = 'uk_price_paid_simple') AND active
ORDER BY name ASC;
前述のドキュメントのクエリ結果から、この例のテーブルにはアクティブなパーツが4つあり、各パーツは最初に挿入されたパーツを1回マージして作成されたことがわかります。
   ┌─name────────┬─level─┬────rows─┐
1. │ all_0_5_1   │     1 │ 6368414 │
2. │ all_12_17_1 │     1 │ 6442494 │
3. │ all_18_23_1 │     1 │ 5977762 │
4. │ all_6_11_1  │     1 │ 6459763 │
   └─────────────┴───────┴─────────┘
クエリを実行すると、4 つのパーツがその後 1 つの最終的なパーツにマージされたことがわかります (テーブルへのそれ以上の insert がない場合) :
   ┌─name───────┬─level─┬─────rows─┐
1. │ all_0_23_2 │     2 │ 25248433 │
   └────────────┴───────┴──────────┘
ClickHouse 24.10 では、新しいマージダッシュボードが組み込みの監視ダッシュボードに追加されました。これは OSS と Cloud の両方で /merges HTTP ハンドラー経由で利用でき、この例のテーブルで発生するすべてのパーツマージを可視化できます。
上のダッシュボードの録画では、最初のデータ挿入から単一パーツへの最終的なマージまで、プロセス全体を確認できます。 ① アクティブなパーツ数。 ② ボックスで視覚的に表したパーツマージ (サイズはパーツの大きさを反映) 。 書き込み増幅

並行マージ

1 台の ClickHouse サーバーは、複数のバックグラウンドマージスレッドを使用して、パーツの並行マージを実行します。
各マージスレッドは、次のループを実行します。 ① 次にマージするパーツを決定し、それらをメモリに読み込みます。 ② メモリ内のパーツを、より大きなパーツへマージします。 ③ マージ後のパーツをディスクに書き込みます。 ① に戻る CPU コア数と RAM 容量を増やすことで、バックグラウンドマージのスループットを高められる点に注意してください。

メモリ最適化されたマージ

ClickHouse では、前の例で示したように、マージ対象のすべてのパーツを必ずしも一度にメモリへ読み込むわけではありません。いくつかの要因に応じて、メモリ消費を抑えるために (そのぶんマージ速度は低下します) 、いわゆる垂直マージでは、パーツを一括で処理するのではなく、ブロックの chunk ごとに読み込んでマージします。

マージの仕組み

以下の図は、ClickHouse における単一のバックグラウンドマージスレッドが、パーツをどのようにマージするかを示しています (デフォルトでは、垂直マージは使用しません) :
パーツマージは、いくつかのステップで実行されます: ① 解凍と読み込み:マージ対象のパーツに含まれる圧縮済みバイナリカラムファイルを解凍し、メモリに読み込みます。 ② マージ:データを、より大きなカラムファイルへマージします。 ③ 索引作成:マージ後のカラムファイルに対して、新しいスパースプライマリインデックスが生成されます。 ④ 圧縮と保存:新しいカラムファイルと索引は圧縮され、マージ後のデータパーツを表す新しいディレクトリに保存されます。 また、セカンダリのデータスキッピングインデックス、カラム STATISTICS、チェックサム、min-max 索引 などのdata parts 内の追加メタデータも、マージ後のカラムファイルに基づいて再作成されます。ここでは簡略化のため、これらの詳細は省略しています。 ステップ②の仕組みは、使用するMergeTree エンジンによって異なります。これは、エンジンごとにマージの処理方法が異なるためです。たとえば、古くなった行が集計されたり、置き換えられたりすることがあります。前述のとおり、このアプローチではすべてのデータ処理をバックグラウンドマージにオフロードするため、書き込み処理を軽量かつ効率的に保ち、超高速な書き込みを実現できます。 次に、MergeTree family に属する各エンジンのマージの仕組みを簡単に見ていきます。

標準マージ

以下の図は、標準的なMergeTreeテーブルでパーツがどのようにマージされるかを示しています。
上図のDDLステートメントは、ソートキーが (town, street)MergeTree テーブルを作成します。つまり、ディスク上のデータはこれらのカラムでソートされ、それに応じてスパースプライマリインデックスが生成されることを意味します ① 圧縮解除され、あらかじめソートされたテーブルのカラムが、テーブルのソートキーで定義された全体のソート順を維持したまま ② マージされ、③ 新しいスパースプライマリインデックスが生成され、④ マージ後のカラムファイルと索引が圧縮されて、ディスク上の新しいデータパーツとして保存されます。

Replacing マージ

ReplacingMergeTree テーブルのパートマージは、標準的なマージ と同様に動作しますが、各行については最新バージョンのみが保持され、古いバージョンは破棄されます。
上の図の DDL ステートメントは、ソートキー (town, street, id) を持つ ReplacingMergeTree テーブルを作成しています。これは、ディスク上のデータがこれらのカラム順にソートされ、それに応じてスパースプライマリインデックスが生成されることを意味します。 ② のマージ処理は、標準的な MergeTree テーブルと同様に動作し、グローバルなソート順を維持しながら、解凍済みで事前にソートされたカラムを結合します。 ただし、ReplacingMergeTree では同じソートキーを持つ重複行が削除され、その行を含むパートの作成タイムスタンプに基づいて最新の行だけが保持されます。

加算マージ

数値データは、SummingMergeTree テーブルのパーツのマージ時に自動的に集計されます。
上の図の DDLステートメントでは、town をソートキーとする SummingMergeTree テーブルを定義しています。これは、ディスク上のデータがこのカラムでソートされ、それに対応するスパースプライマリインデックスが作成されることを意味します。 ② のマージ処理では、ClickHouse は同じソートキーを持つすべての行を 1 つの行に置き換え、数値カラムの値を合計します。

集計マージ

前述の SummingMergeTree テーブルの例は、AggregatingMergeTree テーブルの特化版であり、パートのマージ時に 90+ の任意の集計関数を適用することで、データの自動インクリメンタル変換 を可能にします。
上の図の DDL ステートメントは、town をソートキーとする AggregatingMergeTree テーブルを作成し、このカラムに基づいてデータがディスク上で順序付けられ、対応するスパースプライマリインデックスが生成されるようにします。 ② のマージ中、ClickHouse は同じソートキーを持つすべての行を、部分集計状態 を格納した 1 行に置き換えます (たとえば、avg() に対する sumcount) 。これらの状態により、インクリメンタルなバックグラウンドマージを通じて正確な結果が保証されます。
最終更新日 2026年6月10日