dbtのincrementalマテリアライゼーションは、既存データを再処理せず新規・更新分だけを書き込むことで、処理時間とコストを下げる仕組みです。仕組み自体はシンプルですが、ユニークキー、更新検出ロジック、戦略(merge/append/insert_overwrite)の選択、スキーマ変更への対応までを一貫して設計しないと事故につながります。
本稿では、dbt公式ドキュメントの安定機能に基づき、増分の基本から、主要ウェアハウス(Snowflake/BigQuery/Databricks/Spark/Redshift)での実装の違い、試験で狙われやすいポイントまでを一気に整理します。
incrementalは、モデルをテーブルとして物理化し、以後の実行で新規・更新データのみをターゲット表に反映します。初回はフルビルド、2回目以降は差分のみを処理するのが基本動作です。
差分判定は、モデルSQL内のis_incremental()ブロックの条件、もしくはアダプタが提供する戦略(mergeやinsert_overwrite)で行われます。安全な運用のためには、ユニークキーと更新検出列の定義が肝になります。
| マテリアライゼーション | ストレージ消費 | 再ビルド時間の目安 |
|---|---|---|
| view | ゼロ(計算時のみ) | 高速(常時クエリ評価) |
| table | 大 | フル再計算が必要で重い |
| incremental | 中 | 差分のみで短い |
| ephemeral | ゼロ | 上流にインライン展開(依存先の負荷次第) |
増分ビルドの概念フロー
最小構成: incrementalモデルの雛形
{{ config(
materialized='incremental'
) }}
with src as (
select * from {{ ref('stg_orders') }}
)
select * from src
{% if is_incremental() %}
-- 2回目以降にだけ適用される差分条件をここに記述
-- 例: updated_atがターゲットの最大値以降
where updated_at >= (
select coalesce(max(updated_at), '1970-01-01') from {{ this }}
)
{% endif %}更新検出は代表的に2通りあります。タイムスタンプの水位(最大updated_at)で切る方法、あるいはユニークキーで衝突行をマージ(UPSERT)する方法です。前者は単純で高速、後者は重複や遅延到着データに強いのが特徴です。
dbtではunique_keyを設定し、incremental_strategy='merge'を選ぶと、サポートされるアダプタではMERGE文相当でのUPSERTが自動生成されます。is_incremental()のwhere条件とMERGEの双方を組み合わせ、読み取り量と整合性のバランスを最適化します。
| 方式 | 利点 | 注意点 |
|---|---|---|
| 水位(タイムスタンプ) | 単純・速い・スキャン削減 | 時計ずれ/遅延到着で取りこぼしリスク |
| ユニークキー+MERGE | 重複や更新上書きに強い | キー未定義や不安定キーは不可 |
| ハイブリッド | I/O削減と整合性の両立 | where漏れ時はfull-refreshが保険 |
is_incremental の分岐イメージ
MERGEを使うUPSERTテンプレート
{{ config(
materialized='incremental',
incremental_strategy='merge',
unique_key='order_id'
) }}
with src as (
select * from {{ ref('stg_orders') }}
{% if is_incremental() %}
where updated_at >= (
select coalesce(max(updated_at), '1970-01-01') from {{ this }}
)
{% endif %}
)
select * from srcincremental_strategyは、アダプタ(ウェアハウス)の能力に合わせて選びます。サポート状況はバージョンにより変わるため、実環境のアダプタドキュメントを確認してください。以下は安定的に知られる傾向です。
mergeはユニークキーが必要で、更新・挿入を同時に扱えます。appendは常に追記のみで、重複排除は上流のSQLで担保が必要です。insert_overwriteはパーティション単位で置換でき、BigQueryやSpark系でよく使われます。
| 戦略 | 主な対応エンジン例 | 要件/注意点 |
|---|---|---|
| merge | Snowflake, BigQuery, Databricks/Spark, Redshift | unique_key必須。更新と挿入を同時処理 |
| append | 全般 | 重複は自身で排除。更新は不可(上書きなし) |
| insert_overwrite | BigQuery, Databricks/Spark | partition_byが前提。対象パーティションを置換 |
戦略選択のざっくりフロー
戦略ごとの設定例
-- Snowflake: MERGE
{{ config(materialized='incremental', incremental_strategy='merge', unique_key='id') }}
-- BigQuery: INSERT_OVERWRITE(日次分のみ置換)
{{ config(
materialized='incremental',
incremental_strategy='insert_overwrite',
partition_by={'field': 'ds', 'data_type': 'date'},
cluster_by=['customer_id']
) }}
-- Spark/Databricks: MERGE
{{ config(materialized='incremental', incremental_strategy='merge', unique_key='id') }}本番運用では列の追加・型変更が起こり得ます。dbtはon_schema_changeで基本動作を制御できます。サポート内容はアダプタに依存するため、より安全な選択肢をデフォルトにするのが無難です。
破壊的変更(型縮小や列削除など)や、ロジックの大幅変更時は、dbt run --full-refreshでの再作成が確実です。
| 設定値 | 挙動の概要 | 使いどころ |
|---|---|---|
| ignore | 何もしない | データ品質優先で保守的にしたい時 |
| append_new_columns | 新規列は追加 | 列追加が頻発し後方互換な場合 |
| sync_all_columns | 差分を同期 | スキーマ追随を自動化したい場合(要アダプタ対応) |
| fail | 差分で失敗 | CIで検知・手当てを強制したい場合 |
スキーマ進化時の分岐
on_schema_change と full-refresh の例
{{ config(
materialized='incremental',
incremental_strategy='merge',
unique_key='id',
on_schema_change='append_new_columns'
) }}
-- 破壊的変更時はコマンドで再作成
-- dbt run --select my_model --full-refresh増分はI/O削減が主目的です。ウェアハウスのパーティション/クラスタリング機能と組み合わせると、読み取り範囲をさらに狭められます。特にinsert_overwriteはパーティション設計とセットで考えます。
水位は遅延到着分を考慮し、わずかに余裕を持たせた範囲で読むのが実務の定石です。たとえばmax(updated_at)から数時間〜1日戻すなど、環境に合わせてチューニングします。
| エンジン | 推奨の併用設定 | 備考 |
|---|---|---|
| BigQuery | partition_by(ds), cluster_by(user_id) | insert_overwriteと相性が良い |
| Snowflake | クラスタリングキー(必要に応じて) | コストと相談で設定 |
| Databricks/Spark | partitionBy, Z-Ordering(環境依存) | MERGEの対象範囲を絞る |
水位管理のイメージ
Time ----|----|----|---->
^max_in_target
<---- safety window ----
読み取り範囲 = [max_in_target - safety, now]安全マージン付き水位の例(マクロ併用)
{% macro watermark(model, col, safety_hours=6) %}
select datetime_sub(max({{ col }}), interval {{ safety_hours }} hour) as wm
from {{ model }}
{% endmacro %}
-- モデル内
with w as (
{{ watermark(this, 'updated_at', 6) }}
), src as (
select * from {{ ref('stg_orders') }} s
{% if is_incremental() %}
where s.updated_at >= (select coalesce(wm, '1970-01-01') from w)
{% endif %}
)
select * from srcAnalytics Engineer試験では、incrementalの基本動作、unique_keyの必要性、on_schema_change、full-refresh、戦略選択の是非が狙われやすいです。文章問題では、どの戦略を選ぶべきか、どんな設定が不足しているかを問われます。
実務では、意図しない重複や取りこぼし、スキーマ進化時の黙殺が典型的な障害要因です。CIでfull-refreshも混ぜた検証、データプロファイルの監視をセットにするのが安全です。
| キーワード | よくある誤答/落とし穴 | 正解の指針 |
|---|---|---|
| merge | unique_key不要だと思い込む | mergeには安定したunique_keyが要る |
| insert_overwrite | 任意の表で常に使えると誤解 | パーティション列が前提。対象分だけ置換 |
| on_schema_change | 常にsync_all_columnsで安全 | アダプタ対応を確認。安全側に倒す |
戦略選定の意思決定ミニツリー
検証と運用のコマンド例
# 差分のみ
dbt run --select tag:incremental
# フル再作成(影響小さい単位で)
dbt run --select my_model --full-refresh
# テストと組み合わせ
dbt test --select state:modified+Analytics Engineer
問題 1
BigQuery上の日次パーティション(ds列, DATE)を持つファクト表を、過去分は不変・当日分のみ差し替えたい。処理時間を最小化しつつ重複や取りこぼしを避ける推奨設定はどれか。
正解: A
日次パーティションの当日分のみを置換する要件には、BigQueryのinsert_overwriteが適合します。partition_byの定義が必要です。appendは重複や当日更新に弱く、mergeにunique_key未設定は不適切、tableのフル再作成は不要に重いです。
unique_keyは常に必要ですか?
いいえ。merge戦略では必須ですが、appendやinsert_overwriteのみで済む設計(不変・追記専用、またはパーティション置換)なら必須ではありません。ただし更新上書きが必要なら安定したunique_keyを用意してください。
遅延到着データで取りこぼしが起きないようにするには?
水位をmax(updated_at)から数時間〜1日戻して読む、もしくは差分whereで直近期間だけ読み、mergeで最終整合を取るハイブリッドが有効です。取りこぼしが疑われる場合は一時的に範囲を広げるかfull-refreshで整合を回復します。
on_schema_changeの安全な既定値はどれですか?
運用方針次第ですが、まずはfailかignoreで保守的にし、列追加時は明示的にappend_new_columnsへ切り替えるのが無難です。sync_all_columnsは便利ですがアダプタ対応と変更内容を確認してから有効化してください。
NicheeLab編集部
データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。
dbt Model の基礎: SQL で定義する変換の最小単位
Analytics Engineer 向けに、dbt Model の定義、マテリアライゼーション、依存関係、インクリメン...
dbt Analytics Engineer 試験ガイド: 出題範囲・配点・申込の実務視点
dbt Analytics Engineer 認定の出題範囲、配点の考え方、申込から受験までの流れを、公式ドキュメントの...
dbt Cloud と dbt Core の違いと選び方:Analytics Engineer 試験に効く要点
dbt Cloud と dbt Core の機能差を、実務と資格対策の両面から整理。スケジューリング、IDE、RBAC、...
dbt プロジェクト構造ガイド: models / seeds / macros の実務レイアウト
Analytics Engineer 向けに、dbt プロジェクトのディレクトリ構造と命名規約、dbt_project....
dbt_project.yml の読み方:主要設定と命名を最短で掴む
dbt_project.yml の必須キー、命名解決(database.schema.identifier)、設定優先度...