ユーザーからよく寄せられる質問の 1 つに、materialized view と プロジェクションのどちらを使うべきか、というものがあります。この記事では、この 2 つの主な違いと、 状況に応じてどちらを選ぶべきかを説明します。
主な違いの要約
| Aspect | Materialized views | Projections |
|---|---|---|
| データの保存場所と配置 | 結果は独立した明示的ターゲットテーブルに保存され、ソーステーブルへの INSERT に対する INSERT 時の insert trigger として動作します。 | プロジェクションは最適化されたデータレイアウトを作成し、メインテーブルのデータと一緒に物理的に保存され、ユーザーからは見えません。 |
| 更新メカニズム | ソーステーブルへの INSERT に対して同期的に動作します (インクリメンタルmaterialized view の場合) 。注: リフレッシュ可能なマテリアライズドビューを使ってスケジュールすることもできます。 | メインテーブルへの INSERT に応じて、バックグラウンドで非同期に更新されます。 |
| クエリとの関わり | materialized view を利用するには、ターゲットテーブルを直接クエリする必要があります。つまり、クエリを書く際には materialized view の存在を意識しておく必要があります。 | プロジェクションは ClickHouse のクエリオプティマイザによって自動的に選択され、透過的に機能します。そのため、利用するためにユーザーがプロジェクションを持つテーブルへのクエリを変更する必要はありません。バージョン 25.6 以降では、複数のプロジェクションでフィルタすることも可能です。 |
UPDATE / DELETE の扱い | materialized view はソーステーブルを認識せず、ソーステーブル への insert trigger としてのみ動作するため、ソーステーブル上の UPDATE や DELETE 操作には自動では反応しません。このため、ソーステーブルとターゲットテーブルの間でデータが古くなる可能性があり、回避策または定期的なフルリフレッシュが必要です。 (リフレッシュ可能なマテリアライズドビュー経由) | デフォルトでは、DELETED 行と互換性がありません (特に論理削除) 。lightweight_mutation_projection_mode (v24.7+) で互換性を有効にできます。 |
JOIN のサポート | はい。リフレッシュ可能なマテリアライズドビューは複雑な非正規化に使用できます。インクリメンタルmaterialized view は、左端のテーブルへの insert でのみトリガーされます。 | いいえ。マテリアライズされたデータをフィルタするためのプロジェクション定義内では JOIN 操作はサポートされていません。ただし、プロジェクションを持つテーブルを join するクエリ自体は通常どおり動作します。プロジェクションは個々のテーブルアクセスを最適化します。 |
定義内の WHERE clause | はい。マテリアライズ前にデータをフィルタするために WHERE 句を含めることができます。 | いいえ。マテリアライズされたデータをフィルタするためのプロジェクション定義内では WHERE 句はサポートされていません。 |
| 連鎖の可否 | はい。1 つの materialized view のターゲットテーブルを別の materialized view のソースとして使えるため、多段 pipeline を構築できます。 | いいえ。プロジェクションは連鎖できません。 |
| 適用可能なテーブルエンジン | さまざまなソーステーブルエンジンで使用できますが、ターゲットテーブルは通常 MergeTree ファミリーです。 | MergeTree ファミリーのテーブルエンジンでのみ利用可能です。 |
| 障害時の扱い | データ挿入中に障害が発生すると、ターゲットテーブル内のデータが失われ、不整合が生じる可能性があります。 | 障害はバックグラウンドで透過的に処理されます。クエリはマテリアライズ済みパーツと未マテリアライズのパーツをシームレスに混在させて扱えます。 |
| 運用上のオーバーヘッド | 明示的なターゲットテーブルの作成が必要で、多くの場合は手動での backfill も必要です。UPDATE/DELETE との整合性管理によって複雑さが増します。 | プロジェクションは自動的に維持され、同期も保たれるため、一般に運用負荷は低くなります。 |
FINAL クエリとの互換性 | 一般には互換性がありますが、ターゲットテーブルに対して GROUP BY が必要になることがよくあります。 | FINAL クエリでは動作しません。 |
| 遅延マテリアライゼーション | はい。 | マテリアライゼーション機能を使う場合は、プロジェクションとの互換性の問題に注意してください。query_plan_optimize_lazy_materialization = false の設定が必要になる場合があります。 |
| 並列レプリカ | はい。 | いいえ。 |
optimize_read_in_order | はい。 | はい。 |
| 論理更新と論理削除 | はい。 | いいえ。 |
materialized view とプロジェクションの比較
materialized viewを選ぶべき場合
- リアルタイムETLと多段データパイプラインに対応する場合: 複雑な変換や集計を実行する必要がある、または到着したデータをルーティングする必要があり、viewを連鎖させて複数段階にまたがる処理を行う可能性がある場合。
- 複雑な非正規化が必要な場合: 複数のソース (テーブル、サブクエリ、または辞書) のデータを、クエリに最適化された単一のテーブルに事前に結合する必要があり、特にリフレッシュ可能なマテリアライズドビューを使用した定期的なフルrefreshを許容できる場合。
- 明示的なスキーマ制御を行いたい場合: 事前計算された結果を格納するために、独自のスキーマとエンジンを持つ別個のターゲットテーブルが必要であり、データモデリングにより高い柔軟性を求める場合。
- インジェスト時にfilterしたい場合: データがmaterializedされる_前に_ filterする必要があり、ターゲットテーブルに書き込まれるデータ量を削減したい場合。
materialized viewを避けるべきケース
- ソースデータが頻繁に更新または削除される: ソーステーブルとターゲットテーブルの整合性を保つための追加の対策がないと、インクリメンタルmaterialized viewが古い状態になり、一貫性が失われる可能性があります。
- シンプルさと自動最適化を重視する: 個別のターゲットテーブルを管理したくない場合です。
プロジェクションを選ぶべき場合
- 単一テーブルのクエリを最適化したい: 主な目的が、別のソート順を用意したり、プライマリキーに含まれないカラムに対するフィルタを最適化したり、単一の基となるテーブルに対する集計を事前計算したりすることで、そのテーブルに対するクエリを高速化することである場合。
- クエリの透過性を求める: クエリを変更せずに元のテーブルを対象とし、指定したクエリに対して最適なデータレイアウトを ClickHouse に選択させたい場合。
プロジェクションを避けるべき場合
- 複雑なデータ変換や多段階の ETL が必要な場合: プロジェクション定義では
JOIN操作はサポートされず、多段階パイプラインを構築するために連結することもできません。また、ウィンドウ関数や複雑なCASEステートメントなど、一部の SQL 機能も扱えません。プロジェクションを持つテーブルに対するクエリでは自由にJOINできますが、プロジェクション自体は複雑なデータ変換には向いていません。 - マテリアライズするデータを明示的に絞り込む必要がある場合: プロジェクションでは、プロジェクション自体にマテリアライズされるデータを絞り込むための
WHERE句を定義に含めることはできません。 - MergeTree 以外のテーブルエンジンを使用している場合: プロジェクションを利用できるのは、
MergeTreeファミリーのエンジンを使用するテーブルのみです。 FINALクエリが不可欠な場合: プロジェクションはFINALクエリでは機能せず、FINALクエリは重複排除のために使われることがあります。- 並列レプリカ が必要な場合: プロジェクションではサポートされていません。