更新戦略の選択
- insert によって更新を処理する 専用のテーブルエンジン を使用する
UPDATE ... SETやALTER TABLE ... UPDATEのような 宣言的な更新 ステートメントを使用する
専用のテーブルエンジンを使う場合
MergeTree ファミリーのテーブルエンジンはバックグラウンドでデータパーツをマージするため、結果整合性 を提供します。そのため、完全にマージされるまでの間にテーブルをクエリする際は、適切に重複排除するために
FINAL キーワードを使用する必要があります。
ほかにもエンジンの種類はありますが、これらが最も一般的に使われます。
宣言的更新を使用するタイミング
UPDATE ステートメントは、重複排除ロジックを管理する複雑さがなく、単純な更新処理ではより扱いやすい場合があります。ただし一般に、専用エンジンを使う方法と比べると、比較的少ない行を低頻度で更新する用途に向いています。
専用のテーブルエンジンを使った更新
ReplacingMergeTree
ReplacingMergeTree は、バックグラウンドマージ時に同じソートキーを持つ行を重複排除し、最新バージョンのみを保持します。
FINAL 修飾子または同等のクエリロジックを使用する必要があります。FINAL 修飾子を使うと、データに応じてクエリのオーバーヘッドが 21~550% 増加します。
ReplacingMergeTree ではソートキーの値は更新できません。また、論理削除用の Deleted カラムもサポートしています。
詳細: ReplacingMergeTree ガイド | ReplacingMergeTree リファレンス
CoalescingMergeTree
Nullable にする必要があります。ReplacingMergeTree と同様に、正しく統合された結果を得るには FINAL を使用してください。
このエンジンは ClickHouse 25.6 から利用できます。
詳細: CoalescingMergeTree
CollapsingMergeTree
CollapsingMergeTree は Sign カラムを使って、マージ時に ClickHouse が行をどのように処理するかを指定します。Sign カラムに -1 が挿入されると、その行は対応する +1 の行と対になったときに折りたたまれ (削除され) ます。更新対象の行は、テーブル作成時に ORDER BY 句で使用するソートキーに基づいて識別されます。
ReplacingMergeTreeとは異なり、CollapsingMergeTreeではソートキーの値を変更できます。金融取引やゲーム状態の追跡など、取り消しを前提とした可逆的な操作に適しています。
上記の更新方法では、打ち消し用の行を挿入するために、アプリケーション側でクライアントの状態を保持しておく必要があります。これは ClickHouse の観点では最も効率的ですが、大規模環境では扱いが複雑になることがあります。また、正しい結果を得るには、クエリでも符号の乗算を伴う集約が必要です。
宣言的更新
ミューテーション
ALTER TABLE ... UPDATE) は、WHERE 式に一致する行を含むすべてのパーツを再書き込みします。
WHERE 式に一致するすべてのパーツを書き換えます。
このプロセスにはアトミック性がありません。
パーツは準備でき次第、変更後のパーツに置き換えられるため、ミューテーションの実行中に開始された SELECT クエリでは、すでにミューテーションされたパーツのデータと、まだミューテーションされていないパーツのデータの両方が見えることになります。
進行状況は system.mutations テーブルで確認できます。
詳細: ALTER TABLE UPDATE
オンザフライミューテーション
ALTER TABLE ... UPDATE によるミューテーションでは、変更後の値がクエリ結果に反映されるまで、バックグラウンドプロセスによってミューテーションが適用されるのを待つ必要がある場合があります。
ClickHouse では、“オンザフライミューテーション” によってこの動作を変更できます。
オンザフライミューテーションを有効にすると、更新された行は即座に更新済みとしてマークされ、後続の SELECT クエリでは変更後の値が自動的に返されます。
オンザフライミューテーションは、クエリレベルの設定 apply_mutations_on_fly を有効にすることで、MergeTree ファミリーのテーブルで使用できます。
例
例
テーブルを作成し、いくつかのミューテーションを実行してみましょう。新しいテーブルに対してクエリを実行した時点では、行の値はまだ更新されていないことに注意してください。次に、オンザフライミューテーションを有効にするとどうなるかを見てみましょう。これで
SELECT クエリで更新結果を確認してみましょう。SELECT クエリは、ミューテーションが適用されるのを待つことなく、直ちに正しい結果を返します。パフォーマンスへの影響
SELECT クエリの実行時にのみ適用されます。ただし、ミューテーションのマテリアライズ自体は引き続きバックグラウンドで非同期に行われており、これは負荷の高い処理である点に注意してください。
一定の期間にわたって、投入されるミューテーション数がバックグラウンドで処理されるミューテーション数を継続的に上回ると、適用が必要な未マテリアライズのミューテーションのキューは増え続けます。その結果、最終的に SELECT クエリのパフォーマンスが低下します。
未マテリアライズのミューテーションが際限なく増え続けるのを防ぐため、設定 apply_mutations_on_fly は、number_of_mutations_to_throw や number_of_mutations_to_delay などの他の MergeTree レベルの設定とあわせて有効にすることを推奨します。
サブクエリおよび非決定論的関数のサポート
mutations_max_literal_size_to_replace で制御されます) 。また、非決定論的関数については、定数となるもののみがサポートされます (例: 関数 now()) 。
これらの動作は、次の設定で制御されます。
論理更新
UPDATE 構文を使用し、マージを待たずにパッチパートを即座に作成します。更新された値は、パッチの適用によって SELECT クエリですぐに参照できますが、物理的に反映されるのは後続のマージ時のみです。そのため、論理更新は、予測可能なレイテンシで少数の行 (テーブルの約 10% まで) を更新するのに最適です。ベンチマークでは、ミューテーションと比べて最大 23 倍高速になることが示されています。
その一方で、SELECT クエリではパッチの適用に伴うオーバーヘッドが発生し、パッチパートはパーツ数の上限にもカウントされます。約 10% のしきい値を超えると、読み取り時のパッチ適用オーバーヘッドが比例して増加するため、より大規模な更新では同期ミューテーションのほうが効率的です。
詳細: Lightweight UPDATE
オンザフライミューテーション
オンザフライミューテーションは、行を更新した際に、その後のSELECTクエリでバックグラウンド処理の完了を待たずに変更後の値が自動的に返される仕組みです。これにより、通常のミューテーションにおけるアトミック性の制約を実質的に解消できます。
SELECT クエリの両方で、apply_mutations_on_fly = 1 設定を有効にする必要があります。ミューテーションの条件は ClickHouse Keeper に保存され、Keeper はそれらをすべてメモリ内に保持し、クエリ実行時にオンザフライで適用します。
データの更新には引き続きミューテーションが使用される点に注意してください。違うのは、すぐには実体化されないということだけです。ミューテーションは非同期プロセスとしてバックグラウンドで引き続き適用され、通常のミューテーションと同じ大きなオーバーヘッドが発生します。また、この操作で使用できる式にも制限があります (詳細を参照) 。
詳しくは: On-the-fly mutations