再試行時の挿入の重複排除
挿入操作は、タイムアウトなどのエラーにより失敗することがあります。挿入が失敗した場合、そのデータが実際に挿入されたかどうかは保証されません。このガイドでは、同じデータが複数回挿入されないように、挿入の再試行時に重複排除を有効にする方法について説明します。
挿入が再試行されると、ClickHouse はそのデータがすでに正常に挿入されているかどうかを判定しようとします。挿入されたデータが重複であるとマークされた場合、ClickHouse はそのデータを宛先テーブルに挿入しません。ただし、ユーザーはデータが通常どおり挿入された場合と同様に、操作が成功したステータスを受け取ります。
制限事項
挿入結果の不確実性
ユーザーは、挿入処理が成功するまでリトライを行う必要があります。すべてのリトライが失敗した場合、データが挿入されたかどうかを判別することはできません。マテリアライズドビューが関係している場合、どのテーブルにデータが現れている可能性があるのかも不明です。マテリアライズドビューとソーステーブルの同期が取れていない状態になっている可能性があります。
重複排除ウィンドウの制限
リトライシーケンスの間に、他の挿入操作が *_deduplication_window を超えて発生した場合、重複排除が意図したとおりに機能しない可能性があります。この場合、同じデータが複数回挿入されることがあります。
再試行時の挿入重複排除を有効化する
テーブルに対する挿入重複排除
挿入時の重複排除をサポートするのは *MergeTree エンジンのみです。
*ReplicatedMergeTree エンジンでは、挿入重複排除はデフォルトで有効化されており、replicated_deduplication_window および replicated_deduplication_window_seconds の各設定で制御されます。レプリケートされていない *MergeTree エンジンでは、重複排除は non_replicated_deduplication_window 設定で制御されます。
上記の設定は、テーブルに対する重複排除ログのパラメータを決定します。重複排除ログには有限個の block_id が保存されており、これによって重複排除の動作が決まります(詳細は後述)。
クエリレベルでの挿入重複排除
insert_deduplicate=1 設定により、クエリレベルでの重複排除が有効になります。insert_deduplicate=0 でデータを挿入した場合、そのデータは、たとえ insert_deduplicate=1 を指定して挿入を再試行しても重複排除されません。これは、insert_deduplicate=0 での挿入時にはブロックに対して block_id が書き込まれないためです。
挿入時の重複排除の仕組み
データが ClickHouse に挿入されると、行数およびバイト数に基づいてデータはブロックに分割されます。
*MergeTree エンジンを使用するテーブルでは、各ブロックに一意の block_id が割り当てられます。これは、そのブロック内のデータに対するハッシュです。この block_id は挿入操作の一意キーとして使用されます。同じ block_id が重複排除ログ内に存在する場合、そのブロックは重複と見なされ、テーブルには挿入されません。
このアプローチは、挿入ごとに異なるデータが含まれているケースでは有効です。しかし、同じデータを意図的に複数回挿入する必要がある場合、insert_deduplication_token 設定を使用して重複排除プロセスを制御する必要があります。この設定により、挿入ごとに一意のトークンを指定でき、ClickHouse はこれを使用してデータが重複かどうかを判断します。
INSERT ... VALUES クエリでは、挿入されたデータをブロックに分割する処理は決定論的であり、設定によって決まります。そのため、ユーザーは最初の操作と同じ設定値を使用して挿入を再試行する必要があります。
INSERT ... SELECT クエリでは、クエリの SELECT 部分が各操作で同じ順序の同じデータを返すことが重要です。これは実運用では達成が難しい点に注意してください。再試行時のデータ順序を安定させるために、クエリの SELECT 部分で厳密な ORDER BY 句を定義してください。再試行の間に、参照しているテーブルが更新される可能性があることも考慮する必要があります。この場合、結果データが変化し、重複排除は行われません。さらに、大量のデータを挿入する状況では、挿入後のブロック数が重複排除ログのウィンドウを超えてしまい、ClickHouse がブロックを重複排除すべきかどうか判断できなくなる可能性があります。
マテリアライズドビューによる挿入の重複排除
テーブルに 1 つ以上のマテリアライズドビューがある場合、挿入されたデータには定義された変換が適用され、その結果がこれらのビューの出力先にも挿入されます。変換後のデータも、リトライ時に重複排除されます。ClickHouse は、ターゲットテーブルに挿入されたデータを重複排除する場合と同様の方法で、マテリアライズドビューに対しても重複排除を行います。
この処理は、ソーステーブルに対して次の設定を使用することで制御できます:
replicated_deduplication_windowreplicated_deduplication_window_secondsnon_replicated_deduplication_window
あわせて、ユーザープロファイルの設定項目 deduplicate_blocks_in_dependent_materialized_views を有効にする必要があります。
insert_deduplicate=1 を有効にすると、ソーステーブル内で挿入データの重複排除が行われます。deduplicate_blocks_in_dependent_materialized_views=1 を設定すると、従属テーブル内での重複排除も追加で有効になります。完全な重複排除を行うには、両方の設定を有効にする必要があります。
マテリアライズドビュー配下のテーブルにブロックを挿入する際、ClickHouse はソーステーブルの block_id と追加の識別子を結合した文字列をハッシュすることで block_id を計算します。これにより、マテリアライズドビュー内で正確な重複排除が保証され、マテリアライズドビュー配下の宛先テーブルに到達する前にどのような変換が適用されていても、元の挿入に基づいてデータを区別できるようになります。
例
マテリアライズドビューでの変換により同一になったブロック
マテリアライズドビュー内部での変換中に生成された同一のブロックは、異なる挿入データに基づいているため、重複排除されません。
次に例を示します。
上記の設定により、各ブロックが 1 行のみを含む一連のブロックから成るテーブルを対象に選択処理を行えるようになります。これらの小さなブロックは圧縮されず、テーブルに挿入されるまで同じ状態のまま保持されます。
マテリアライズドビューで重複排除を有効にする必要があります。
ここでは、dst テーブルに 2 つのパーツが挿入されたことが分かります。SELECT からは 2 ブロック、INSERT では 2 つのパーツです。これらのパーツには互いに異なるデータが含まれています。
ここでは、mv_dst テーブルに 2 つのパーツが挿入されていることが分かります。これらのパーツには同じデータが含まれていますが、重複排除は行われていません。
ここでは、挿入の再試行を行うと、すべてのデータが重複排除されていることが分かります。重複排除は dst および mv_dst テーブルの両方で行われます。
挿入時の同一ブロック
挿入:
┌─'from dst'─┬─key─┬─value─┬─_part─────┐ │ from dst │ 0 │ A │ all_0_0_0 │ └────────────┴─────┴───────┴───────────┘
挿入:
同一のブロックが2つ、想定どおりに挿入されました。
再試行された挿入は期待どおりに重複排除されます。
その挿入は、挿入されたデータが異なっていても同様に重複排除されます。insert_deduplication_token の方が優先される点に注意してください。insert_deduplication_token が指定されている場合、ClickHouse はデータのハッシュ値を使用しません。
異なる挿入操作によっても、マテリアライズドビューの基になるテーブルでの変換後のデータが同一になる場合
SET deduplicate_blocks_in_dependent_materialized_views=1;
select 'first attempt';
INSERT INTO dst VALUES (1, 'A');
SELECT 'from dst', *, _part FROM dst ORDER by all;
┌─'from dst'─┬─key─┬─value─┬─_part─────┐ │ from dst │ 1 │ A │ all_0_0_0 │ └────────────┴─────┴───────┴───────────┘
SELECT 'from mv_dst', *, _part FROM mv_dst ORDER by all;
┌─'from mv_dst'─┬─key─┬─value─┬─_part─────┐ │ from mv_dst │ 0 │ A │ all_0_0_0 │ └───────────────┴─────┴───────┴───────────┘
select 'second attempt';
INSERT INTO dst VALUES (2, 'A');
SELECT 'from dst', *, _part FROM dst ORDER by all;
┌─'from dst'─┬─key─┬─value─┬─_part─────┐ │ from dst │ 1 │ A │ all_0_0_0 │ │ from dst │ 2 │ A │ all_1_1_0 │ └────────────┴─────┴───────┴───────────┘
SELECT 'from mv_dst', *, _part FROM mv_dst ORDER by all;
┌─'from mv_dst'─┬─key─┬─value─┬─_part─────┐ │ from mv_dst │ 0 │ A │ all_0_0_0 │ │ from mv_dst │ 0 │ A │ all_1_1_0 │ └───────────────┴─────┴───────┴───────────┘
テーブル mv_dst に、同一のブロックが 2 つ挿入されました(想定どおりです)。
┌─'from dst'─┬─key─┬─value─┬─_part─────┐ │ from dst │ 1 │ A │ all_0_0_0 │ └────────────┴─────┴───────┴───────────┘
SELECT 'from mv_dst', *, _part FROM mv_dst ORDER by all;
┌─'from mv_dst'─┬─key─┬─value─┬─_part─────┐ │ from mv_dst │ 0 │ A │ all_0_0_0 │ │ from mv_dst │ 0 │ A │ all_1_1_0 │ └───────────────┴─────┴───────┴───────────┘