このページでは、プロジェクションの概要、使用方法、およびプロジェクションを操作するための各種オプションについて説明します。
プロジェクションは、クエリ実行を最適化するフォーマットでデータを格納します。この機能は、次のような場合に役立ちます。
- 主キーに含まれないカラムに対してクエリを実行する
- カラムを事前集計し、計算量と IO の両方を削減する
テーブルには 1 つ以上のプロジェクションを定義でき、クエリ解析時には、スキャン対象のデータ量が最も少ないプロジェクションが、ユーザーが指定したクエリを変更することなく ClickHouse によって選択されます。
ディスク使用量プロジェクションは内部的に新しい非表示テーブルを作成するため、より多くの IO とディスク容量が必要になります。
たとえば、プロジェクションで異なる主キーを定義した場合、元のテーブルのすべてのデータが複製されます。
プロジェクションが内部でどのように動作するかについて、より技術的な詳細はこのページで確認できます。
テーブルを作成します。
CREATE TABLE visits_order
(
`user_id` UInt64,
`user_name` String,
`pages_visited` Nullable(Float64),
`user_agent` String
)
ENGINE = MergeTree()
PRIMARY KEY user_agent
ALTER TABLE を使用すると、既存のテーブルにプロジェクションを追加できます。
ALTER TABLE visits_order ADD PROJECTION user_name_projection (
SELECT *
ORDER BY user_name
)
ALTER TABLE visits_order MATERIALIZE PROJECTION user_name_projection
データの挿入:
INSERT INTO visits_order SELECT
number,
'test',
1.5 * (number / 2),
'Android'
FROM numbers(1, 100);
プロジェクションを使うと、元のテーブルで user_name が PRIMARY_KEY として定義されていなくても、user_name で高速に絞り込めます。
クエリ時には、データが user_name 順に並んでいるため、プロジェクションを使ったほうが処理されるデータ量が少なくなると ClickHouse が判断します。
SELECT
*
FROM visits_order
WHERE user_name='test'
LIMIT 2
クエリでプロジェクションが使用されているかを確認するには、system.query_log テーブルを確認します。projections フィールドには、使用されたプロジェクション名が入り、何も使用されていない場合は空です。
SELECT query, projections FROM system.query_log WHERE query_id='<query_id>'
プロジェクション projection_visits_by_user を持つテーブルを作成します:
CREATE TABLE visits
(
`user_id` UInt64,
`user_name` String,
`pages_visited` Nullable(Float64),
`user_agent` String,
PROJECTION projection_visits_by_user
(
SELECT
user_agent,
sum(pages_visited)
GROUP BY user_id, user_agent
)
)
ENGINE = MergeTree()
ORDER BY user_agent
データを挿入します:
INSERT INTO visits SELECT
number,
'test',
1.5 * (number / 2),
'Android'
FROM numbers(1, 100);
INSERT INTO visits SELECT
number,
'test',
1. * (number / 2),
'IOS'
FROM numbers(100, 500);
フィールド user_agent を使って、GROUP BY を含む最初のクエリを実行します。
このクエリでは、事前集計が一致しないため、定義したプロジェクションは使用されません。
SELECT
user_agent,
count(DISTINCT user_id)
FROM visits
GROUP BY user_agent
projection を利用するには、事前集計と GROUP BY のフィールドの一部またはすべてを選択するクエリを実行できます。
SELECT
user_agent
FROM visits
WHERE user_id > 50 AND user_id < 150
GROUP BY user_agent
SELECT
user_agent,
sum(pages_visited)
FROM visits
GROUP BY user_agent
前述のとおり、プロジェクションが使用されたかどうかは system.query_log テーブルを確認することで把握できます。
projections フィールドには、使用されたプロジェクションの名前が表示されます。
プロジェクションが使用されていない場合、このフィールドは空になります。
SELECT query, projections FROM system.query_log WHERE query_id='<query_id>'
プロジェクション索引を作成するには、次のようにします。
CREATE TABLE events
(
`event_time` DateTime,
`event_id` UInt64,
`user_id` UInt64,
`huge_string` String,
PROJECTION order_by_user_id INDEX user_id TYPE basic
)
ENGINE = MergeTree()
ORDER BY (event_id);
いくつかのサンプルデータを挿入します:
INSERT INTO events SELECT * FROM generateRandom() LIMIT 100000;
_part_offset フィールドは、マージやミューテーションを経てもその値が保持されるため、セカンダリ索引で有用です。これはクエリで活用できます:
SELECT
count()
FROM events
WHERE _part_starting_offset + _part_offset IN (
SELECT _part_starting_offset + _part_offset
FROM events
WHERE user_id = 42
)
SETTINGS enable_shared_storage_snapshot_in_query = 1
プロジェクションに対しては、次の操作を実行できます:
以下のステートメントを使用して、テーブルのメタデータにプロジェクションの説明を追加します。
ALTER TABLE [db.]name [ON CLUSTER cluster] ADD PROJECTION [IF NOT EXISTS] name ( SELECT <COLUMN LIST EXPR> [GROUP BY] [ORDER BY] ) [WITH SETTINGS ( setting_name1 = setting_value1, setting_name2 = setting_value2, ...)]
WITH SETTINGS ではプロジェクションレベルの設定を定義し、プロジェクションでのデータの格納方法 (たとえば index_granularity や index_granularity_bytes) をカスタマイズできます。
これらは MergeTree のテーブル設定 に直接対応していますが、適用されるのはこのプロジェクションのみです。
例:
ALTER TABLE t
ADD PROJECTION p (
SELECT x ORDER BY x
) WITH SETTINGS (
index_granularity = 4096,
index_granularity_bytes = 1048576
);
プロジェクション設定は、検証ルールに従い、当該プロジェクションに適用される実効的なテーブル設定を上書きします (例: 無効または互換性のない上書きは拒否されます) 。
以下のステートメントを使用して、テーブルのメタデータからプロジェクションの定義を削除し、ディスク上のプロジェクションファイルを削除します。
これは ミューテーション として実装されています。
ALTER TABLE [db.]name [ON CLUSTER cluster] DROP PROJECTION [IF EXISTS] name
以下のステートメントを使用して、パーティション partition_name のプロジェクション name を再構築します。
これは mutation として実装されています。
ALTER TABLE [db.]table [ON CLUSTER cluster] MATERIALIZE PROJECTION [IF EXISTS] name [IN PARTITION partition_name]
説明を削除せずにディスク上のプロジェクションファイルを削除するには、以下のステートメントを使用します。
これは mutation として実装されています。
ALTER TABLE [db.]table [ON CLUSTER cluster] CLEAR PROJECTION [IF EXISTS] name [IN PARTITION partition_name]
ADD、DROP、CLEAR コマンドは、メタデータの変更またはファイルの削除だけを行うという意味で軽量です。
また、これらのコマンドはレプリケーションされ、ClickHouse Keeper または ZooKeeper を介してプロジェクションのメタデータを同期します。
クエリを実行すると、ClickHouse は元のテーブルまたはそのプロジェクションのいずれから読み取るかを選択します。
元のテーブルとそのプロジェクションのどちらから読み取るかの判断は、各テーブルパーツごとに個別に行われます。
ClickHouse は一般に、できるだけ少ないデータを読み取ることを目指しており、たとえばパーツの主キーをサンプリングするなど、最適な読み取り元のパーツを特定するためにいくつかの手法を用います。
場合によっては、元のテーブルのパーツに対応するプロジェクションパーツが存在しないことがあります。
これは、たとえば SQL でテーブルにプロジェクションを作成する処理がデフォルトで「遅延的」だからです。つまり、新たに挿入されるデータにのみ影響し、既存のパーツは変更されません。
いずれかのプロジェクションには事前計算済みの集約値がすでに含まれているため、ClickHouse はクエリ実行時に再度集約を行うことを避けるために、対応するプロジェクションパーツから読み取ろうとします。特定のパーツに対応するプロジェクションパーツがない場合、クエリ実行は元のパーツにフォールバックします。
しかし、元のテーブル内の行が、単純ではない data part のバックグラウンドマージによって大きく変化した場合はどうなるでしょうか。
たとえば、そのテーブルが ReplacingMergeTree テーブルエンジンを使用して保存されているとします。
マージ中に同じ行が複数の入力パーツで検出された場合、もっとも新しい行バージョン (もっとも最近に挿入されたパーツのもの) だけが保持され、それ以前のすべてのバージョンは破棄されます。
同様に、テーブルが AggregatingMergeTree テーブルエンジンを使用して保存されている場合、マージ操作によって、入力パーツ内の同じ行が主キーの値に基づいて 1 行にまとめられ、部分的な集約状態が更新されることがあります。
ClickHouse v24.8 より前では、プロジェクションパーツはメインデータと気づかれないまま同期がずれるか、あるいは更新や削除などの特定の操作をまったく実行できませんでした。これは、テーブルにプロジェクションがある場合、データベースが自動的に例外を送出していたためです。
v24.8 以降では、新しいテーブルレベル設定 deduplicate_merge_projection_mode により、前述の単純ではないバックグラウンドマージ操作が元のテーブルのパーツで発生した場合の動作を制御できます。
Delete mutations も、元のテーブルのパーツ内の行を削除する part merge 操作の別の例です。v24.7 以降では、論理削除によってトリガーされる delete mutations に関する動作を制御する設定もあります: lightweight_mutation_projection_mode。
以下は、deduplicate_merge_projection_mode と lightweight_mutation_projection_mode の両方で設定可能な値です。
throw (デフォルト): 例外が送出され、プロジェクションパーツの同期ずれを防ぎます。
drop: 影響を受けるプロジェクションテーブルパーツは削除されます。影響を受けたプロジェクションパーツについては、クエリは元のテーブルパーツにフォールバックします。
rebuild: 影響を受けたプロジェクションパーツは、元のテーブルパーツ内のデータとの整合性を保つために再構築されます。
プロジェクションのORDER BY句では、ALIASカラムは使用できません。例:
CREATE TABLE t
(
id UInt64,
a UInt32,
ab_sum UInt64 ALIAS a + 1,
PROJECTION p (SELECT a ORDER BY ab_sum)
)
ENGINE = MergeTree ORDER BY id;
-- UNKNOWN_IDENTIFIERで失敗する
ALIAS カラムは物理的に保存されず、クエリ時にその場で計算されるため、ソート式が評価されるプロジェクションパートの書き込み処理では使用できません。
代わりに、MATERIALIZED カラムを使用するか、式を直接埋め込んでください。
-- MATERIALIZEDカラムを使用する場合
CREATE TABLE t
(
id UInt64,
a UInt32,
ab_sum UInt64 MATERIALIZED a + 1,
PROJECTION p (SELECT a ORDER BY ab_sum)
)
ENGINE = MergeTree ORDER BY id;
-- インライン式を使用する場合
CREATE TABLE t
(
id UInt64,
a UInt32,
PROJECTION p (SELECT a ORDER BY a + 1)
)
ENGINE = MergeTree ORDER BY id;