メインコンテンツまでスキップ
メインコンテンツまでスキップ

重複除去戦略

重複除去とは、データセットの重複行を削除するプロセスを指します。OLTPデータベースでは、各行に一意の主キーがあるため、これは簡単に行えますが、その代償として挿入が遅くなります。挿入された各行は、まず検索され、見つかった場合は置き換えられる必要があります。

ClickHouseは、データの挿入に関してスピードを重視して設計されています。ストレージファイルは不変であり、ClickHouseは行を挿入する前に既存の主キーをチェックしないため、重複除去にはもう少し手間がかかります。これにより、重複除去は即時ではなく、最終的に行われるため、いくつかの副作用があります。

  • いつでもテーブルには重複(同じソートキーを持つ行)が存在する可能性があります
  • 重複行の実際の削除は、パーツのマージ中に行われます
  • クエリは重複の可能性を許容する必要があります
Cassandra logoClickHouseは重複除去やその他多くのトピックに関する無料トレーニングを提供しています。 データの削除と更新トレーニングモジュールは良い出発点です。

重複除去のオプション

重複除去は、ClickHouseで以下のテーブルエンジンを使用して実装されています。

  1. ReplacingMergeTreeテーブルエンジン: このテーブルエンジンを使用すると、同じソートキーを持つ重複行がマージ中に削除されます。ReplacingMergeTreeは、最後に挿入された行を返すクエリを模倣するのに適しています。

  2. 行の崩壊: CollapsingMergeTreeおよびVersionedCollapsingMergeTreeテーブルエンジンは、既存の行が「キャンセルされ」、新しい行が挿入されるというロジックを使用します。これらはReplacingMergeTreeよりも実装が複雑ですが、データがマージされているかどうかを気にせずにクエリや集計を簡単に書くことができます。頻繁にデータを更新する必要があるときに、この2つのテーブルエンジンは便利です。

以下でこれらのテクニックの詳細を説明します。詳細については、無料のオンデマンドデータの削除と更新トレーニングモジュールをチェックしてください。

アップサートのためのReplacingMergeTreeの使用

Hacker Newsのコメントを保存しているテーブルがあり、viewsカラムがコメントが表示された回数を示しているシンプルな例を見てみましょう。記事が公開されたときに新しい行を挿入し、もし値が増加する場合は毎日1回、合計表示回数で新しい行をアップサートするとします。

2行を挿入しましょう:

viewsカラムを更新するには、同じ主キーで新しい行を挿入します(viewsカラムの新しい値に注意してください):

テーブルには現在4行があります:

上記の出力の別々のボックスは、内部での2つの部分を示しています - このデータはまだマージされていないため、重複行はまだ削除されていません。次に、SELECTクエリでFINALキーワードを使用すると、クエリ結果の論理的なマージが実行されます:

結果は2行だけで、最後に挿入された行が返されます。

注記

FINALを使用するのはデータ量が少ない場合には問題ありませんが、大量のデータを扱う場合はFINALは最適な選択ではないかもしれません。カラムの最新値を見つけるためのより良い選択肢について話しましょう…

FINALの回避

両方のユニーク行のviewsカラムを再度更新しましょう:

テーブルは現在6行あります。なぜなら、実際のマージはまだ発生しておらず(FINALを使った時のクエリ時のマージのみ)、現在の行に対する実体的なマージはまだ行われていないからです。

FINALの代わりに、ビジネスロジックを使用します。viewsカラムが常に増加するとわかっているので、max関数を使用して最大値を持つ行を選択し、必要なカラムでグループ化します:

このようにグループ化することで、クエリパフォーマンスの点でFINALを使用するよりも効率的である場合があります。

私たちのデータの削除と更新トレーニングモジュールは、この例を拡張し、ReplacingMergeTreeを使用してversionカラムをどのように利用するかについて説明します。

カラムを頻繁に更新するためのCollapsingMergeTreeの使用

カラムを更新することは、既存の行を削除し、新しい値で置き換えることを含みます。すでに見たように、この種の変更はClickHouseでは_最終的に_行われ、マージ中に発生します。多くの行を更新する必要がある場合、ALTER TABLE..UPDATEを避けて、既存のデータと並行して新しいデータを挿入する方が効率的です。データが古いか新しいかを示すカラムを追加できます…実際、これを非常にうまく実装しているテーブルエンジンがあります。特に、古いデータを自動的に削除してくれる点が優れています。どのように機能するのか見てみましょう。

Hacker Newsのコメントの表示回数を外部システムで追跡し、数時間ごとにそのデータをClickHouseにプッシュする場合を考えます。古い行を削除し、新しい行が各Hacker Newsコメントの新しい状態を表すようにしたいとします。この動作を実装するために、CollapsingMergeTreeを使用します。

表示回数を保存するためのテーブルを定義しましょう:

hackernews_viewsテーブルにはsignという名前のInt8カラムがあります。これは符号カラムと呼ばれます。この名前は任意ですが、Int8データ型は必須です。また、カラム名はCollapsingMergeTreeテーブルのコンストラクタに渡されます。

CollapsingMergeTreeテーブルの符号カラムは何ですか?行の_状態_を表し、符号カラムは1または-1のみを取ります。動作は次のようになります:

  • 二つの行が同じ主キー(主キーが異なる場合はソート順)を持ち、符号カラムが異なる場合、+1で挿入された最後の行が状態行となり、他の行がキャンセルされます
  • キャンセルされた行はマージ中に削除されます
  • 対応するペアがない行は残ります

hackernews_viewsテーブルに行を追加しましょう。主キーについて唯一の行であるため、状態を1に設定します:

その後、表示回数カラムを変更したいとします。既存の行をキャンセルする行と、新しい状態を含む行を含めて二つの行を挿入します:

テーブルには現在、主キー(123, 'ricardo')で3行あります:

FINALを追加すると、現在の状態行が返されます:

もちろん、大きなテーブルに対してFINALを使用するのは推奨されません。

注記

この例で挿入したviewsカラムの値は実際に必要ではなく、古い行のviewsの現在の値と一致する必要もありません。実際には、主キーと-1だけで行をキャンセルできます:

複数のスレッドからのリアルタイム更新

CollapsingMergeTreeテーブルでは、符号カラムを使って行がキャンセルされ、行の状態は最後に挿入された行によって決定されます。しかし、異なるスレッドから行を挿入している場合、行の挿入順序が入れ替わる可能性があるため、これは問題です。「最後」の行を使用することは、この状況ではうまく機能しません。

ここで役立つのがVersionedCollapsingMergeTreeです。これは、CollapsingMergeTreeのように行をキャンセルしますが、最後に挿入された行の代わりに、指定されたバージョンカラムの最大値を持つ行を維持します。

例を見てみましょう。Hacker Newsコメントの表示回数を追跡し、データが頻繁に更新される場合を考えます。最新の値を使用したいが、マージを強制したり待ったりしたくないとします。CollapsingMergeTreeに似たテーブルから始めますが、行の状態を示すバージョンを保存するカラムを追加します:

テーブルはVersionedCollapsingMergeTreeをエンジンとして使用し、符号カラムバージョンカラムを渡します。以下は、このテーブルの動作です:

  • 同じ主キーとバージョンおよび異なる符号を持つ各行のペアが削除されます
  • 行が挿入された順序は重要ではありません
  • バージョンカラムが主キーの一部でない場合、ClickHouseはそれを暗黙的に主キーの最後のフィールドとして追加します

クエリを書く際にも同じタイプのロジックを使用します-主キーでグループ化し、キャンセルされているがまだ削除されていない行を避けるために巧妙なロジックを使用します。hackernews_views_vcmtテーブルにいくつかの行を追加しましょう:

次に、二つの行を更新し、そのうちの一つを削除します。行をキャンセルするには、前のバージョン番号を含める必要があります(主キーの一部であるため):

符号カラムに基づいて値をうまく加算および減算するクエリを以前と同じように実行します:

結果は二行です:

テーブルをマージすることを強制しましょう:

結果には二行しか存在しないはずです:

VersionedCollapsingMergeTreeテーブルは、複数のクライアントやスレッドから行を挿入しながら重複除去を実装したい場合に非常に便利です。

なぜ行が重複除去されないのか?

挿入された行が重複除去されない理由の一つは、INSERTステートメントに冪等性のない関数または式を使用している場合です。たとえば、createdAt DateTime64(3) DEFAULT now()カラムで行を挿入している場合、各行はcreatedAtカラムに一意のデフォルト値を持つため、重複しないことが保証されます。MergeTree / ReplicatedMergeTreeテーブルエンジンは、各挿入された行が一意のチェックサムを生成するため、行を重複除去する方法を理解できません。

この場合、同じバッチの行が再挿入されないようにしっかりと検証するために、各バッチのために独自のinsert_deduplication_tokenを指定できます。この設定の詳細については、insert_deduplication_tokenに関するドキュメントを参照してください。