メインコンテンツへスキップ
このセクションでは、dbt と ClickHouse アダプターのセットアップ方法に関するガイドに加え、一般公開されている IMDB データセットを使用して ClickHouse で dbt を利用する例を紹介します。この例では、次の手順を扱います。
  1. dbt プロジェクトを作成し、ClickHouse アダプターをセットアップする。
  2. モデルを定義する。
  3. モデルを更新する。
  4. incremental モデルを作成する。
  5. snapshot モデルを作成する。
  6. materialized view を使用する。
これらのガイドは、ドキュメント機能と設定、および マテリアライゼーション リファレンス とあわせて利用することを想定しています。

セットアップ

環境を準備するには、dbt と ClickHouse アダプターのセットアップ セクションの手順に従ってください。 重要: 以下の内容は Python 3.9 でテストされています。

ClickHouse を準備する

dbt は、リレーショナル性の高いデータのモデリングで特に威力を発揮します。例として、以下のリレーショナルスキーマを持つ小規模な IMDB データセットを用意しています。このデータセットは relational dataset repository から取得したものです。dbt で一般的に扱うスキーマと比べると非常に単純ですが、手頃なサンプルとして適しています。 これらのテーブルの一部を、以下に示すように使用します。 次のテーブルを作成します。
テーブル rolescreated_at カラムには、デフォルト値として now() が設定されています。これは後でモデルのインクリメンタル更新を識別するために使用します。詳しくはインクリメンタルモデルを参照してください。
s3 関数を使用して、公開エンドポイントからソースデータを読み込み、データを挿入します。次のコマンドを実行してテーブルにデータを投入してください。
これらの実行時間は帯域幅によって異なる場合がありますが、いずれも完了まで数秒しかかからないはずです。以下のクエリを実行して、映画への出演数が多い順に各俳優の集計結果を算出し、データが正常に読み込まれたことを確認します。
レスポンスは次のようになります。
以降のガイドでは、このクエリをモデルに変換し、dbt のビューおよびテーブルとして ClickHouse にマテリアライズします。

ClickHouse への接続

  1. dbt プロジェクトを作成します。この例では、imdb ソースにちなんで名前を付けます。プロンプトが表示されたら、データベースとして clickhouse を選択します。
  2. プロジェクトフォルダーに cd します。
  3. この時点で、お好みのテキストエディターが必要です。以下の例では、広く使われている VS Code を使用します。IMDB ディレクトリを開くと、yml ファイルと sql ファイルが一式表示されるはずです。
  4. dbt_project.yml ファイルを更新し、最初のモデル actor_summary を指定して、プロファイルを clickhouse_imdb に設定します。
  5. 次に、dbt に ClickHouse インスタンスの接続情報を指定する必要があります。以下を ~/.dbt/profiles.yml に追加してください。
    user と password は変更が必要である点に注意してください。利用可能な追加の設定はこちらに記載されています。
  6. IMDB ディレクトリから dbt debug コマンドを実行し、dbt が ClickHouse に接続できるかどうかを確認します。
    接続に成功したことを示す Connection test: [OK connection ok] が応答に含まれていることを確認してください。

シンプルなビュー マテリアライゼーションの作成

ビュー マテリアライゼーションを使用する場合、モデルは実行のたびに ClickHouse で CREATE VIEW AS ステートメントによってビューとして再構築されます。これにはデータを追加で保存する必要はありませんが、テーブル マテリアライゼーションよりクエリは遅くなります。
  1. imdb フォルダ内の models/example ディレクトリを削除します:
  2. models フォルダ内の actors に新しいファイルを作成します。ここでは、それぞれが actor モデルを表すファイルを作成します。
  3. models/actors フォルダに schema.ymlactor_summary.sql を作成します。
    ファイル schema.yml でテーブルを定義します。これらは以降、マクロで利用できるようになります。 models/actors/schema.yml を次の内容に編集してください:
    actors_summary.sql では、実際のモデルを定義します。config 関数では、このモデルを ClickHouse で view として materialize するよう指定している点にも注意してください。各テーブルは、schema.yml ファイルから関数 source を介して参照されます。たとえば source('imdb', 'movies') は、imdb データベース内の movies テーブルを参照します。models/actors/actors_summary.sql を編集して、次の内容にしてください:
    最終的な actor_summary にカラム updated_at を含めている点に注目してください。これは後で増分マテリアライゼーションに利用します。
  4. imdb ディレクトリで dbt run コマンドを実行します。
  5. dbt は、指定どおりにこのモデルを ClickHouse のビューとして表現します。これで、このビューに直接クエリを実行できます。このビューは imdb_dbt データベースに作成されます。これは、clickhouse_imdb プロファイル配下の ~/.dbt/profiles.yml ファイルにあるスキーマ パラメータによって決まります。
    このビューに対してクエリを実行すると、よりシンプルな構文で先ほどのクエリと同じ結果を再現できます:

テーブルとしてのマテリアライゼーションの作成

前の例では、モデルはビューとしてマテリアライズされました。これは一部のクエリには十分なパフォーマンスを提供する場合もありますが、より複雑なSELECTや頻繁に実行されるクエリでは、テーブルとしてマテリアライズしたほうが適していることがあります。このマテリアライゼーションは、BIツールからクエリされるモデルに適しており、ユーザーにより高速な操作性を提供できます。これにより、クエリ結果は新しいテーブルとして保存され、その分のストレージオーバーヘッドが発生します。つまり、実質的には INSERT TO SELECT が実行されます。このテーブルは毎回再構築されるため、増分ではない点に注意してください。そのため、結果セットが大きい場合は実行時間が長くなる可能性があります。詳細は dbt Limitations を参照してください。
  1. actors_summary.sql ファイルを修正し、materialized パラメータが table に設定されるようにします。ORDER BY がどのように定義されているか、また MergeTree テーブルエンジンを使用していることに注目してください。
  2. imdb ディレクトリで dbt run コマンドを実行します。この処理にはやや時間がかかる場合があり、ほとんどのマシンでは約 10 秒です。
  3. imdb_dbt.actor_summary テーブルが作成されたことを確認します。
    適切なデータ型を持つテーブルが表示されるはずです。
  4. このテーブルの結果が前のレスポンスと一致していることを確認します。モデルがテーブルになったことで、レスポンス時間が大幅に改善されていることがわかります。
    このモデルに対して、他のクエリも自由に実行してみてください。たとえば、出演回数が 5 回を超える俳優のうち、平均評価が最も高いのは誰でしょうか。

インクリメンタルマテリアライゼーションの作成

前の例では、モデルをマテリアライズするためのテーブルを作成しました。このテーブルは、dbt の実行のたびに再構築されます。結果セットが大きい場合や変換が複雑な場合、これは現実的でなく、コストも非常に高くなる可能性があります。この課題に対処してビルド時間を短縮するために、dbt にはインクリメンタルマテリアライゼーションが用意されています。これにより、dbt は前回の実行以降にテーブル内のレコードを挿入または更新できるため、イベント形式のデータに適しています。内部的には、まず更新されたすべてのレコードを含む一時テーブルが作成され、その後、変更されていないすべてのレコードと更新済みのレコードが新しいターゲットテーブルに挿入されます。そのため、大きな結果セットでは、テーブルモデルと同様の制約があります。 大規模なセットでこれらの制約を回避するために、アダプターは inserts_only モードをサポートしています。このモードでは、一時テーブルを作成せずに、すべての更新がターゲットテーブルに挿入されます (詳細は後述します) 。 この例を示すために、俳優「Clicky McClickHouse」を追加します。彼は驚異の 910 本の映画に出演し、Mel Blanc をも上回る出演本数になります。
  1. まず、モデルを incremental 型に変更します。この変更には、次の対応が必要です。
    1. unique_key - アダプターが行を一意に識別できるようにするには、unique_key を指定する必要があります。この場合は、クエリ内の id フィールドで十分です。これにより、マテリアライズされたテーブル内で行の重複が発生しないようにできます。一意性制約の詳細については、こちらを参照してください。
    2. Incremental filter - また、増分実行時にどの行が変更されたかを dbt がどのように識別するかも指定する必要があります。これは、差分を表す式を指定することで実現します。通常、イベントデータでは timestamp を使うため、ここでは updated_at timestamp フィールドを使用します。このカラムは、行が挿入されると既定で now() の値が設定されるため、新しい行を特定できます。さらに、新しいアクターが追加されるケースも検出する必要があります。既存のマテリアライズされたテーブルを表す {{this}} 変数を使うと、式は where id > (select max(id) from {{ this }}) or updated_at > (select max(updated_at) from {{this}}) になります。これを {% if is_incremental() %} 条件内に埋め込むことで、増分実行時にのみ使用され、テーブルの初回作成時には使用されないようにしています。増分モデルで行をフィルタリングする詳細については、dbt ドキュメントのこちらの説明を参照してください。
    actor_summary.sql ファイルを以下のように更新します。
    このモデルは、roles テーブルと actors テーブルに対する更新と追加にのみ応答する点に注意してください。すべてのテーブルに対応させるには、このモデルを複数のサブモデルに分割し、それぞれに独自の増分条件を設定することをおすすめします。こうしたモデルは、さらに相互に参照したり接続したりできます。モデルの相互参照の詳細については、こちらを参照してください。
  2. dbt run を実行し、生成されたテーブルの結果を確認します:
  3. ここで、増分更新を示すために、モデルにデータを追加します。actors テーブルに、俳優 “Clicky McClickHouse” を追加してください:
  4. “Clicky”をランダムに選んだ910本の映画に出演させましょう:
  5. 基になるソーステーブルを直接クエリし、dbtモデルを介さずに、彼が現在、本当に最も出演回数の多い俳優であることを確認します:
  6. dbt run を実行し、モデルが更新されており、上記の結果と一致していることを確認します:

内部

上記の増分更新を実現するために実行されたステートメントは、ClickHouseのクエリログを照会することで特定できます。
上記のクエリは、実行する期間に合わせて調整してください。結果の確認はユーザーに委ねますが、増分更新を行うためにアダプターが使用する一般的な戦略を以下に示します。
  1. アダプターは一時テーブル actor_sumary__dbt_tmp を作成します。変更された行はこのテーブルに書き込まれます。
  2. 次に、新しいテーブル actor_summary_new, が作成されます。続いて、古いテーブルの行が古いテーブルから新しいテーブルへ書き込まれますが、その際、一時テーブルに行 ID が存在しないことを確認します。これにより、更新と重複を効果的に処理できます。
  3. 一時テーブルの結果が、新しい actor_summary テーブルに書き込まれます。
  4. 最後に、新しいテーブルは EXCHANGE TABLES ステートメントを介して古いバージョンとアトミックに入れ替えられます。その後、古いテーブルと一時テーブルは削除されます。
これは以下の図のようになります。 この戦略は、非常に大規模なモデルでは課題が生じる可能性があります。詳細については、制限事項 を参照してください。

Append 戦略 (insert のみモード)

増分モデルで大規模なデータセットを扱う際の制約を回避するため、アダプターでは dbt の設定パラメーター incremental_strategy を使用します。これには append を設定できます。この値を設定すると、更新された行はターゲットテーブル (つまり imdb_dbt.actor_summary) に直接 insert され、一時テーブルは作成されません。 注: append only モードを使うには、データが不変であるか、重複を許容できる必要があります。変更された行に対応する増分テーブルモデルが必要な場合は、このモードは使用しないでください。 このモードを説明するために、新しい俳優をもう 1 人追加し、incremental_strategy='append' を指定して dbt run を再実行します。
  1. actor_summary.sql で append only モードを設定します:
  2. もう 1 人の有名な俳優、Danny DeBito を追加します
  3. Danny をランダムに選んだ 920 本の映画に出演させます。
  4. dbt run を実行し、Danny が actor_summary テーブルに追加されたことを確認します
この増分処理が “Clicky” の insert と比べて大幅に高速であることに注目してください。 query_log テーブルをもう一度確認すると、2 回の増分実行の違いが分かります:
この実行では、新しい行だけが直接 imdb_dbt.actor_summary テーブルに追加され、テーブルは作成されません。

削除と挿入モード (実験的)

これまでの ClickHouse では、更新と削除のサポートは、非同期の Mutations による限定的なものにとどまっていました。これは非常に IO 負荷が高くなる可能性があるため、通常は避けるべきです。 ClickHouse 22.8 では 論理削除、ClickHouse 25.7 では 論理更新 が導入されました。これらの機能により、単一の更新クエリによる変更は、非同期で実体化される場合でも、ユーザーからは即座に反映されたように見えます。 このモードは、たとえば incremental_strategy パラメータでモデルに設定できます。
この戦略は対象の model のテーブルを直接操作するため、処理中に問題が発生すると、incremental model 内のデータが無効な状態になる可能性があります。つまり、atomic な更新ではありません。 要約すると、このアプローチでは次の処理を行います。
  1. アダプターが一時テーブル actor_sumary__dbt_tmp を作成します。変更された行はこのテーブルに書き込まれます。
  2. 現在の actor_summary テーブルに対して DELETE を実行します。actor_sumary__dbt_tmp にある id をもとに行が削除されます。
  3. actor_sumary__dbt_tmp の行を、INSERT INTO actor_summary SELECT * FROM actor_sumary__dbt_tmp を使って actor_summary に挿入します。
このプロセスを以下に示します。

insert_overwrite モード (実験的)

次の手順を実行します。
  1. incremental モデルのリレーションと同じ構造を持つステージング (一時) テーブルを作成します: CREATE TABLE {staging} AS {target}
  2. 新しいレコード (SELECT によって生成されたもの) のみをステージングテーブルに挿入します。
  3. 新しいパーティション (ステージングテーブルに存在するもの) のみをターゲットテーブル内で置き換えます。

このアプローチには、次の利点があります。
  • テーブル全体をコピーしないため、デフォルトの戦略より高速です。
  • INSERT 操作が正常に完了するまで元のテーブルを変更しないため、他の戦略より安全です。途中で障害が発生した場合でも、元のテーブルは変更されません。
  • データエンジニアリングにおける「パーティション不変性」というベストプラクティスに沿っています。これにより、インクリメンタル処理や並列データ処理、ロールバックなどが簡単になります。

スナップショットの作成

dbtのスナップショットを使用すると、変更可能なモデルに対する変更履歴を時系列で記録できます。これにより、モデルに対して特定時点のクエリを実行でき、アナリストはモデルの過去の状態を「時点をさかのぼって」確認できます。これは、行がいつ有効だったかを開始日と終了日のカラムに記録する type-2 Slowly Changing Dimensions を使用して実現されます。この機能は ClickHouse アダプターでサポートされており、以下で説明します。 この例では、Creating an Incremental Table Model を完了していることを前提としています。actor_summary.sqlinserts_only=True を設定していないことを確認してください。models/actor_summary.sql は次のようになっているはずです:
  1. snapshots ディレクトリに actor_summary ファイルを作成します。
  2. actor_summary.sql ファイルの内容を、以下のように更新します。
この内容について、いくつか補足します。
  • select クエリは、時間の経過に沿ってスナップショットとして保持したい結果を定義します。ref 関数は、先ほど作成した actor_summary モデルを参照するために使用します。
  • レコードの変更を示すために、timestamp カラムが必要です。ここでは updated_at カラム (インクリメンタルテーブルモデルの作成 を参照) を使用できます。strategy パラメータは、更新の判定に timestamp を使用することを示し、updated_at パラメータは使用するカラムを指定します。これがモデルに存在しない場合は、代わりに check strategy を使用することもできます。これはかなり非効率で、比較対象とするカラムの一覧をユーザーが指定する必要があります。dbt はこれらのカラムの現在値と履歴値を比較し、変更があれば記録します (同一であれば何もしません) 。
  1. dbt snapshot コマンドを実行します。
actor_summary_snapshot テーブルが snapshots DB に作成されていることに注目してください (これは target_schema パラメータによって決まります) 。
  1. このデータをサンプリングすると、dbt によって dbt_valid_from と dbt_valid_to というカラムが追加されていることがわかります。後者の値は null になっています。以降の実行でこれが更新されます。
  2. お気に入りの俳優、Clicky McClickHouseをさらに10本の映画に出演させましょう。
  3. imdb ディレクトリで dbt run コマンドを再実行します。これにより、インクリメンタルモデルが更新されます。完了したら、変更を記録するために dbt snapshot を実行します。
  4. ここでスナップショットをクエリすると、Clicky McClickHouse に対応する行が 2 行あることがわかります。前のエントリには dbt_valid_to の値が入るようになりました。新しい値は、dbt_valid_from カラムに同じ値で記録され、dbt_valid_to の値は null になっています。新しい行があった場合は、それらもスナップショットに追加されます。
dbtのスナップショットの詳細については、こちらをご覧ください。

seed の使用

dbt には、CSVファイルからデータを読み込む機能があります。この機能は、database の大規模なエクスポートを読み込む用途には適しておらず、主にコード表や ディクショナリ などで使われる小さなファイル向けに設計されています。たとえば、国コードを国名に対応付けるようなケースです。簡単な例として、seed 機能を使ってジャンルコードの一覧を生成し、アップロードします。
  1. 既存のデータセットからジャンルコードの一覧を生成します。dbt ディレクトリで、clickhouse-client を使用して seeds/genre_codes.csv ファイルを作成します。
  2. dbt seed コマンドを実行します。これにより、CSV ファイルの行を使って、database imdb_dbt に新しい table genre_codes が作成されます (スキーマ設定で定義したとおりです) 。
  3. これらが読み込まれたことを確認します。

追加情報

これまでのガイドで紹介しているのは、dbt の機能のほんの一部にすぎません。詳しくは、充実した dbt documentation をご覧ください。
最終更新日 2026年6月12日