materialized_view マテリアライゼーションは、既存の (ソース) テーブルに対する SELECT である必要があります。PostgreSQL とは異なり、ClickHouse の materialized view は「静的」ではなく (対応する REFRESH 操作もありません) 、挿入トリガー として機能します。つまり、ソーステーブルに挿入された行に対して、定義された SELECT 変換を適用し、その結果として新しい行をターゲットテーブルに挿入します。ClickHouse における materialized view の動作の詳細については、ClickHouse materialized view のドキュメントを参照してください。
一般的なマテリアライゼーションの概念と共通の設定 (engine、order_by、partition_by など) については、Materializations ページを参照してください。
ターゲットテーブルの管理方法
materialized_view マテリアライゼーションを使用する場合、dbt-clickhouse では materialized view と、変換後の行が挿入される ターゲットテーブル の両方を作成する必要があります。ターゲットテーブルの管理方法は 2 つあります。
どちらのアプローチを選ぶかによって、スキーマ変更、フルリフレッシュ、複数 MV 構成の扱い方が変わります。以下のセクションでは、それぞれのアプローチについて詳しく説明します。
暗黙的ターゲットによるマテリアライズ
materialized_view モデルを定義すると、アダプターは次の処理を行います。
- モデル名で ターゲットテーブル を作成します
<model_name>_mvという名前の ClickHouse materialized view を作成します
SELECT ステートメント内のカラムから推論されます。すべてのリソース (ターゲットテーブル + MV) は、同じモデル設定を共有します。
複数のmaterialized view
UNION を構成し、各materialized view の SQL を --my_mv_name:begin と --my_mv_name:end の形式のコメントで囲みます。
たとえば、以下の例では 2 つのmaterialized view が作成され、どちらもそのモデルの同じ宛先テーブルにデータを書き込みます。materialized view の名前は <model_name>_mv1 および <model_name>_mv2 の形式になります。
ターゲットテーブルのスキーマを段階的に変更する方法
dbt run が MV の SQL 内で異なるカラムを検出した際に、ターゲットテーブルのスキーマをどのように段階的に変更するかを制御できます。
ignore) 。ただし、この設定を変更することで、incremental モデルにおける on_schema_change 設定と同じ挙動にできます。
また、この設定は安全策として使うこともできます。これを fail に設定すると、MV の SQL 内のカラムが、最初の dbt run で作成されたターゲットテーブルと異なる場合、ビルドは失敗します。
データのキャッチアップ
catchup=True) 。この動作は、catchup 設定を False にすることで無効にできます。
明示的ターゲットによるマテリアライゼーション (ベータ)
- すべてのリソース (ターゲットテーブル + MV) は同じ設定を共有します。複数の MV が同じターゲットテーブルを参照する場合は、
UNION ALL構文を使ってまとめて定義する必要があります。 - これらのリソースを個別に反復処理することはできず、すべて同じモデルファイルで管理する必要があります。
- 各 MV の名前を簡単に制御することはできません。
- すべての設定がターゲットテーブルと MV の間で共有されるため、各リソースを個別に設定したり、どの設定がどのリソースに対応するのかを把握したりするのが難しくなります。
table マテリアライゼーションとして個別に定義し、その後 materialized view のモデルから参照できます。
利点
- リソースを完全に分離: 各リソースを個別に定義できるようになり、可読性が向上します
- dbt と CH の間で 1:1 のリソース対応: dbt のツールを使って、それぞれを個別に管理し、反復的に改善できるようになりました。
- 異なる設定が利用可能に: それぞれに異なる設定を適用できるようになりました。
- 命名規則を維持する必要がなくなる: MV 用に
_mvを付けたカスタム名ではなく、指定した名前で各リソースが作成されるようになりました。
制限事項
- ターゲットテーブルの定義は dbt の考え方にはあまりなじみません。これはソーステーブルを読み取る SQL ではないため、この部分では dbt の検証が効きません。一方で、MV の SQL 自体は引き続き dbt のユーティリティで検証され、ターゲットテーブルのカラムとの互換性は CH レベルで検証されます。
ref()関数の制約に起因するいくつかの問題が見つかっています: モデル同士を参照するためにこれを使う必要がありますが、参照できるのは上流モデルのみで、下流モデルは参照できません。そのため、この実装ではいくつかの問題が生じます。私たちは dbt-core リポジトリに issue を作成しており、現在 解決策を検討するために dbt 側と協議しています (dbt-labs/dbt-core#12319):ref()を config ブロック内で呼び出すと、共有先のモデルではなく現在のモデルが返されます。このため config() セクション内では定義できず、この依存関係を追加するにはコメントを使わざるを得ません。これは、dbt のドキュメントで示されている 「—depends_on:」アプローチ と同じパターンです。ref()によってターゲットテーブルが先に作成されるため、その点では期待どおりに動作しますが、生成されたドキュメントの依存関係チャートでは、ターゲットテーブルは下流ではなく別の上流依存関係として描画されるため、少し分かりにくくなります。unit-testでも、本来はそこから読み取る想定ではないにもかかわらず、ターゲットテーブル用のデータを定義する必要があります。回避策としては、このテーブルのデータを空のままにしておくだけです。
使用方法
events_daily.sql:
{{ materialization_target_table(ref('events_daily')) }} マクロ呼び出しに注目してください。これは、MV のターゲットテーブルを設定します。
モデル page_events_aggregator.sql:
mobile_events_aggregator.sql:
設定オプション
materialized='table') :
materialized view 側 (
materialized='materialized_view') :
通常、
True に設定するのは、MV 側の catchup と、そのターゲットテーブル側の repopulate_from_mvs_on_full_refresh のどちらか一方だけです。両方を True にすると、データが重複する可能性があります。主な操作
明示的ターゲットを使用した完全リフレッシュ
--full-refresh を使用すると、明示的ターゲットのテーブルは再作成されます (この処理中にインジェストが行われると、データが失われる可能性があります) 。挙動は設定によって異なります。
オプション 1: デフォルトの --full-refresh の動作。すべてが再作成されますが、MV の再作成中はターゲットテーブルが空、または一部しか読み込まれていない状態になります。
すべてが削除され、再作成されます。MV の SQL を使ってデータを再度 insert したい場合は、設定 catchup=True のままにしてください。
catchup=False を設定し、その後 MV に対して dbt run または dbt run --full-refresh を実行できます。ターゲットテーブルに対して --full-refresh を実行する前に、MV が作成されていることを確認してください。これは ClickHouse 上の MV 定義が使われるためです。
ターゲットテーブルの model で repopulate_from_mvs_on_full_refresh=True を設定します。dbt run --full-refresh を実行すると、次の処理が行われます。
- 新しい一時テーブルを作成する
- 各 MV の SQL を使って INSERT-SELECT を実行する
- テーブルをアトミックに入れ替える
ターゲットテーブルの変更
--full-refresh なしで MV のターゲットテーブルを変更することはできません。materialization_target_table() の参照先を変更したあとに通常の dbt run を実行すると、ターゲットが変更されていることを示すエラーメッセージが表示され、ビルドは失敗します。
ターゲットを変更するには、次の手順を実行します。
materialization_target_table()の呼び出しを更新しますdbt run --full-refresh -s your_mv_modelを実行します
よくある問題の対処法
run の実行中または実行後にターゲットテーブルが空になる
- materialized view が
catchup=Falseに設定されているか、ターゲットテーブルがrepopulate_from_mvs_on_full_refresh=Falseに設定されている可能性があります。この場合、materialized view の作成時やターゲットテーブルの再作成時に バックフィル は実行されません。これは想定どおりの動作です。したがって、materialized view の SQL を使ってデータを再度 insert したい場合は、materialized view でcatchup=True(デフォルト値) を設定するか、ターゲットテーブルでrepopulate_from_mvs_on_full_refresh=Trueを設定してください。重複を避けるため、両方を同時に有効にしないでください。詳しくは、configuration セクション を参照してください。 dbt run --full-refreshの実行中に materialized view がデフォルトのcatchup=Trueを使用している場合、ターゲットは再作成され、MV によってデータが順次再度 insert されます。この状況を避けるには、明示的ターゲットでの Full refresh を参照してください。
repopulate_from_mvs_on_full_refresh=True が設定されたターゲットテーブルで dbt run --full-refresh を実行すると、現在プロジェクト内にある SQL ではなく、古い materialized view バージョンのロジックが使用されます
repopulate_from_mvs_on_full_refresh=True は、ClickHouse にすでに定義されている既存の MV の SQL を使用します。新しい materialized view の定義が使われるようにするには、ターゲットテーブルで dbt run --full-refresh を実行する前に、各 materialized view に対して dbt run を実行してください。
実行後に重複データが発生する
- materialized view の
catchup=Trueと、ターゲットテーブルのrepopulate_from_mvs_on_full_refresh=Trueの両方が有効になっている可能性があります。実行したい処理に応じて、どちらか一方のみを有効にしてください。詳細は設定セクションを参照してください。 - ターゲットテーブルが
WHERE 0を付けて定義されていません。ターゲットテーブルは空の状態で作成する必要がありますが、WHERE 0が含まれていないと内部クエリによってデータが挿入される場合があります。該当する句が含まれていることを確認してください。
dbt run --full-refresh の実行後、進行中のインジェスト中にデータが失われる
dbt run --full-refresh の実行後、ソーステーブルの一部の行がターゲットテーブルに反映されていません。
ClickHouse の materialized view は挿入トリガーとして機能し、存在している間しかデータを取り込みません。フルリフレッシュ中は、MV が削除されてから再作成されるまでの短い時間帯 (「ブラインドウィンドウ」) が発生します。この時間帯にソーステーブルへ挿入された行は取り込まれません。詳細については、進行中のインジェスト中の挙動 セクションを参照してください。
デバッグ手法
ClickHouse で MV の現在の書き込み先を確認する
system.tables にクエリします。
dbt がテーブルを materialized view のターゲットとして認識しているか確認する
Table <table_name> is used as a target by a dbt-managed materialized view. Defaulting mv_on_schema_change to “fail” to prevent data loss.
このメッセージが表示された場合、dbt はそのテーブルが少なくとも 1 つの dbt 管理下の materialized view のターゲットになっていることを検出しています。このメッセージが表示されるはずなのに見当たらない場合は、次の点を確認してください。
- materialized view モデルで
{{ materialization_target_table(ref('your_target')) }}が正しく定義されている - materialized view モデルの設定に
materialized='materialized_view'が含まれている - materialized view とターゲットテーブルの両方が、少なくとも 1 回は実行されている
暗黙的ターゲットから明示的ターゲットへの移行
materialized='table' モデルファイルを作成します。空のテーブルを作成するには、WHERE 0 句を使用します。名前は現在の暗黙的 materialized view モデルと同じにしてください。これにより、以後はこのモデルを使ってターゲットテーブルを更新できるようになります。
materialization_target_table() マクロ呼び出しをそれぞれ含む新しいモデルを作成します。以前に UNION ALL を使用していた場合は、その部分とコメントを削除します。
モデル名は、次の命名規則に従う必要があります。
- MV が 1 つだけ定義されていた場合、名前は
<old_model_name>_mvです - 複数の MV が定義されていた場合、各 MV の名前は
<old_model_name>_mv_<name_in_comments>です
my_model.sql (暗黙的ターゲット、UNION ALL を使った単一モデル) :
暗黙的ターゲット方式と明示的ターゲット方式の挙動比較
一般的な挙動
アクティブなインジェスト中の動作
- ClickHouse の materialized view は insert trigger として機能するため、存在している間のデータしか取り込みません。materialized view が削除されて再作成されると (たとえば
--full-refresh中) 、その間にソーステーブルへ挿入された行は materialized view では処理されません。この状態は、materialized view が「blind」であると表現されます。 - 各
catchupプロセスはいずれも、materialized view の SQL を使ったINSERT INTO ... SELECT操作に基づいており、materialized view の動作とは独立しています。INSERTが開始されると、それ以降の新しいデータはその処理では取り込まれませんが、アタッチされた materialized view では取り込まれます。
暗黙的ターゲットでの操作
明示的ターゲットの操作
ターゲットテーブルモデル:
リフレッシャブルmaterialized view
refreshable 設定オブジェクトを MVモデルに追加します。
暗黙的ターゲットを使った例
明示的ターゲットの例
制限事項
- 依存関係を持つリフレッシャブルmaterialized view (MV) を ClickHouse で作成する際、作成時点で指定した依存関係が存在しなくても、ClickHouse は エラーを返しません。代わりに、そのリフレッシャブル MV は非アクティブな状態のままとなり、依存関係が満たされて更新処理やリフレッシュを開始できるようになるまで待機します。 この動作は仕様ですが、必要な依存関係への対応が 速やかに行われない場合、データが利用可能になるまでに遅れが生じる可能性があります。リフレッシャブル materialized view を作成する前に、すべての依存関係が正しく定義され、存在していることを確認してください。
- 現時点では、mv とその依存関係の間に実際の「dbt linkage」はないため、作成順序は 保証されません。
- refreshable 機能は、同じターゲットモデルに向けられた複数の mvs ではテストされていません。