プロジェクション
この記事では、プロジェクションとは何か、その活用方法、およびプロジェクションを操作するためのさまざまなオプションについて説明します。
プロジェクションの概要
プロジェクションは、クエリ実行を最適化する形式でデータを保存します。この機能は次のような用途に有効です:
- プライマリキーに含まれていないカラムに対してクエリを実行する場合
- カラムを事前集約する場合。計算量と I/O の両方を削減できます
テーブルに対して 1 つ以上のプロジェクションを定義できます。クエリ解析時には、ユーザーが指定したクエリを変更することなく、スキャンするデータ量が最も少ないプロジェクションが ClickHouse によって自動的に選択されます。
プロジェクションは内部的に新しい非表示テーブルを作成します。そのため、より多くの I/O とディスク容量が必要になります。 たとえば、プロジェクションで異なるプライマリキーを定義した場合、元のテーブルのすべてのデータが複製されます。
プロジェクションの内部動作に関する、より技術的な詳細はこのページを参照してください。
プロジェクションの使用
プライマリキーを使わずにフィルタリングする例
テーブルの作成:
ALTER TABLE を使って、既存のテーブルに Projection を追加できます。
データの挿入:
この Projection により、元のテーブルで user_name が PRIMARY_KEY として定義されていなくても、user_name で高速にフィルタリングできるようになります。
クエリ実行時に ClickHouse は、データが user_name でソートされているため、Projection を使用した方が処理すべきデータ量が少なくなると判断しました。
クエリが Projection を使用しているか確認するには、system.query_log テーブルを確認します。projections フィールドには、使用された Projection の名前が格納されており、使用されていない場合は空です。
事前集計クエリの例
プロジェクション projection_visits_by_user を持つテーブルを作成します:
データを挿入します:
user_agent フィールドを使用して GROUP BY を行う最初のクエリを実行します。
このクエリでは、事前集計が一致しないため、定義済みの PROJECTION は使用されません。
PROJECTION を活用するには、事前集計および GROUP BY フィールドの一部または全部を選択するクエリを実行できます:
前述のとおり、projection が使用されたかを確認するには、system.query_log テーブルを参照できます。
projections フィールドには、使用された projection の名前が表示されます。
projection が使用されていない場合は、このフィールドは空になります。
_part_offset フィールドを用いた通常のプロジェクション
_part_offset フィールドを利用する通常のプロジェクションを持つテーブルを作成します。
サンプルデータを挿入する:
_part_offset をセカンダリインデックスとして使用する
_part_offset フィールドはマージやミューテーション後も値が保持されるため、セカンダリインデックスとして有用です。クエリでこれを活用できます。
プロジェクションの操作
プロジェクションに対して、次の操作を実行できます。
PROJECTION を追加する
以下のステートメントを使用して、テーブルのメタデータに PROJECTION の定義を追加します。
WITH SETTINGS 句
WITH SETTINGS は PROJECTION レベルの設定を定義し、index_granularity や index_granularity_bytes のような設定によって、PROJECTION がデータをどのように保存するかをカスタマイズします。
これらは MergeTree テーブル設定に直接対応しますが、この PROJECTION に対してのみ適用されます。
例:
Projection の設定は、検証ルールに従う範囲で、その Projection に対して有効となるテーブル設定を上書きします(たとえば、無効または非互換な上書きは拒否されます)。
DROP PROJECTION
以下の文を使用して、テーブルのメタデータからプロジェクションの定義を削除し、ディスクからプロジェクションファイルを削除します。 これは mutation として実装されています。
MATERIALIZE PROJECTION
以下の文を使用すると、パーティション partition_name 内でプロジェクション name を再構築できます。
これは mutation として実装されています。
CLEAR PROJECTION
以下の文を使用すると、定義は削除せずにディスクからプロジェクションファイルを削除できます。 これは mutationとして実装されています。
ADD、DROP、CLEAR コマンドは、メタデータを変更するかファイルを削除するだけの軽量な操作です。
また、これらの操作はレプリケートされ、ClickHouse Keeper または ZooKeeper を介してプロジェクションのメタデータを同期します。
プロジェクションの操作がサポートされるのは、*MergeTree エンジン(replicated バリアントを含む)のテーブルのみです。
プロジェクションのマージ動作の制御
クエリを実行すると、ClickHouse は元のテーブルから読み取るか、そのプロジェクションのいずれかから読み取るかを選択します。 元のテーブルから読み取るか、プロジェクションのいずれかから読み取るかの判断は、各テーブルのパーツごとに個別に行われます。 ClickHouse は一般的に、可能な限り少ないデータを読み取ることを目指しており、どのパーツから読み取るのが最適かを判断するために、パーツのプライマリキーをサンプリングするなど、いくつかの手法を用います。 場合によっては、元のテーブルのパーツに対応するプロジェクションのパーツが存在しないことがあります。 これは例えば、SQL でテーブルに対してプロジェクションを作成する操作が、デフォルトで「遅延的 (lazy)」であり、新しく挿入されるデータにのみ影響し、既存のパーツは変更されないために発生します。
あるプロジェクションにはすでに事前計算済みの集約値が含まれているため、ClickHouse はクエリ実行時に再度集約処理を行うことを避けるために、可能であれば対応するプロジェクションのパーツから読み取ろうとします。特定のパーツに対応するプロジェクションのパーツが存在しない場合、クエリ実行は元のパーツの読み取りにフォールバックします。
では、元のテーブルの行が、バックグラウンドで行われる複雑なデータパーツのマージによって、非単純な形で変更された場合にはどうなるでしょうか。
例えば、そのテーブルが ReplacingMergeTree テーブルエンジンで保存されているとします。
同じ行がマージ対象の複数の入力パーツに存在すると検出された場合、最新の行バージョン(最も新しく挿入されたパーツに含まれるもの)だけが保持され、それ以前のすべてのバージョンは破棄されます。
同様に、テーブルが AggregatingMergeTree テーブルエンジンで保存されている場合、マージ処理では、入力パーツ内で同じ行(プライマリキーの値に基づく)を 1 行に畳み込んで、部分的な集約状態を更新することがあります。
ClickHouse v24.8 より前は、プロジェクションのパーツがメインデータと暗黙のうちに不整合な状態になるか、あるいはテーブルにプロジェクションが存在する場合には、更新や削除などの特定の操作は、データベースが自動的に例外をスローするため、実行できませんでした。
v24.8 以降では、新しいテーブルレベルの設定 deduplicate_merge_projection_mode により、上記のような複雑なバックグラウンドマージ処理が元のテーブルのパーツで発生した場合の動作を制御できます。
削除 mutation も、元のテーブルのパーツ内の行を削除するパーツマージ処理の一例です。v24.7 以降では、論理削除によってトリガーされる削除 mutation に関する動作を制御するための設定 lightweight_mutation_projection_mode も用意されています。
deduplicate_merge_projection_mode と lightweight_mutation_projection_mode に指定可能な値は以下のとおりです:
throw(デフォルト): 例外をスローし、プロジェクションのパーツがメインデータと不整合になることを防ぎます。drop: 影響を受けるプロジェクションテーブルのパーツを削除します。クエリは、影響を受けるプロジェクションパーツについて元のテーブルパーツの読み取りにフォールバックします。rebuild: 影響を受けるプロジェクションパーツを再構築し、元のテーブルパーツ内のデータと整合した状態を保ちます。