dbt入門(第9弾・完結編):個人開発を超える - チーム運用設計の全体像
🚀 はじめに
この記事は dbt入門(第9弾・完結編 / チーム運用設計) です。
ひとまずこの記事で dbt入門シリーズ は完結の予定です!
第9弾の位置づけ(連載の完結編)
これまでの連載で、以下を構築してきました:
- 第1弾:dbtの全体像(staging→marts、materialization)
- 第2弾:dbt Projects on Snowflake実践
- 第3弾:ローカル開発環境(VS Code + Power User for dbt)
- 第4弾:テスト戦略(schema tests / singular tests)
- 第5弾:Claude Code連携(AI駆動の開発)
- 第6弾:docs/lineage(ドキュメント・影響範囲分析)
- 第7弾:CI/CD(GitHub Actions + Slim CI)
- 第8弾:dbt-expectations(データ品質テスト)
ここまでで、「ひとりで動かす環境」は完成しています。
しかし、実務でチーム開発に移行すると、次のような課題に直面します:
-
命名規約がバラバラ:
stg_ordersとstaging_ordersが混在し、構造が読み解けない - PR規約が曖昧:レビューで何を確認すべきか分からず、見落としが発生
- スキーマ爆発:開発者ごとに独自スキーマが増殖し、管理不能に
- 破壊的変更の恐怖:カラム削除が下流に影響し、ダッシュボードが壊れる
- リファクタリングできない:「動いているから触らない」文化が定着し、技術的負債が蓄積
第9弾では、これらの課題を解決する「チーム運用の設計」を実装レベルで整理しました。
この記事でわかること
- 運用設計の3つの柱:命名規約・チーム開発・運用保守の全体像
- 命名規約・フォルダ構成の設計:stg_/int_/fct_/dim_の判断基準とDDD的アプローチ
- チーム開発の運用ルール:ブランチ戦略・PR規約・レビューチェックリスト・スキーマ戦略
- 運用保守の勝ちパターン:リファクタリング・破壊的変更対処・移行シナリオ
- パフォーマンス・コスト管理:incremental・クラスタリング・Warehouse運用
- 第1-8弾の総まとめ+運用Tips:連載の集大成と実務Tips集
対象読者
- dbt入門第1-8弾を読んだ方(または、dbtでローカル開発・CI/CD・テスト環境を構築済みの方)
- 個人開発から複数人チーム開発へ移行したい方
- 運用保守で破綻しない設計を知りたいデータエンジニア・分析基盤担当の方
この記事では、dbtをチームで運用するためのベスプラ・Tips集としてまとめております。
※個人の知見のアウトプットとしての意味合いもあります。
Part 1: dbtプロジェクトの運用設計 - 全体像
運用設計の3つの柱
dbtプロジェクトをチームで運用するには、以下の3つの柱が必要です。

図1: 運用設計の3つの柱。命名規約・フォルダ構成(一貫性)、チーム開発の運用ルール(協働)、運用保守の戦略(持続性)が相互に支え合い、スケールするdbtプロジェクトを実現する。
1. 命名規約・フォルダ構成(一貫性)
目的:プロジェクト構造の一貫性を保ち、新メンバーでも迷わない設計にする
要素:
- モデル命名規約(stg_/int_/fct_/dim_のプレフィックス)
- フォルダ構成(ドメイン駆動設計的なアプローチ)
- メタデータ管理(schema.yml/sources.yml)
効果:
- 「このファイルどこ?」が激減
- モデルの責務が明確になる
- 新メンバーのオンボーディング時間が短縮
2. チーム開発の運用ルール(協働)
目的:複数人が並行して開発できる環境を作り、品質を担保する
要素:
- ブランチ戦略・PR規約
- レビューチェックリスト(dbt特有の観点)
- スキーマ戦略(開発/CI/本番環境の分離)
効果:
- 本番環境の汚染防止
- レビュー観点の標準化
- 並行開発時の競合回避
3. 運用保守の戦略(持続性)
目的:プロジェクトを長期的に保守・改善できる仕組みを作る
要素:
- リファクタリング戦略(モデル名変更・フォルダ再編成)
- 破壊的変更の対処(カラム削除・リネーム)
- パフォーマンス・コスト管理
効果:
- 技術的負債の抑制
- 破壊的変更時の影響範囲を制御
- 運用コストの最適化
第1-8弾で構築した環境の棚卸し

図2: 第1-8弾のタイムライン。各弾で何を構築したかを時系列で可視化。第1弾「全体像」→第3弾「ローカル環境」→第7弾「CI/CD」→第9弾「運用設計」へと進化。
ここまでで構築した環境を整理します:
| 弾 | 構築内容 | チーム運用での位置づけ |
|---|---|---|
| 第1弾 | dbtの全体像(staging→marts、materialization) | ✅ 基礎知識(本記事で再構成) |
| 第2弾 | dbt Projects on Snowflake実践 | △ 学習用 |
| 第3弾 | ローカル開発環境(VS Code + Power User) | ✅ 開発環境(チーム開発の基盤) |
| 第4弾 | テスト戦略(schema tests / singular tests) | ✅ 品質担保(本記事でレビュー観点に組み込む) |
| 第5弾 | Claude Code連携(AI駆動開発) | △ 効率化ツール(チーム運用には直接関係しない) |
| 第6弾 | docs/lineage(ドキュメント・影響範囲分析) | ✅ チーム運用の必須要素(本記事でオンボーディング・レビューに組み込む) |
| 第7弾 | CI/CD(GitHub Actions + Slim CI) | ✅ チーム開発の自動化(本記事でスキーマ戦略と統合) |
| 第8弾 | dbt-expectations(データ品質テスト) | ✅ 品質担保(本記事でテスト戦略に組み込む) |
第9弾でやること:
上記の要素を「チーム運用」の視点で再構成し、以下を追加します:
- 命名規約・フォルダ構成の判断基準
- PR規約・レビューチェックリスト
- スキーマ戦略(開発/CI/本番の分離)
- 破壊的変更の対処法
- リファクタリング戦略
- パフォーマンス・コスト管理
Part 2: 命名規約・フォルダ構成の設計
命名規約の設計(stg_/int_/fct_/dim_の判断基準)
dbtの命名規約には、stg_(staging)、int_(intermediate)、fct_(fact)、dim_(dimension) といったプレフィックスがよく使われます。しかし、「なぜこのプレフィックスを使うのか」 を理解していないと、命名が混乱します。
なぜプレフィックスが必要か?
プレフィックスの目的は、モデルの責務とレイヤーを一目で判別できるようにすることです。
プレフィックスがない場合の問題:
models/
├── orders.sql # ← これは staging? mart?
├── customer.sql # ← これも責務が不明
└── daily_sales.sql # ← 集計テーブル?
- ファイル名だけでは責務が分からない
- 依存関係が読み解けない
- 新メンバーが迷う
プレフィックスがある場合の効果:
models/
├── staging/
│ ├── stg_orders.sql # ← staging層であることが明確
│ └── stg_customers.sql
├── intermediate/
│ └── int_orders__with_customer.sql # ← intermediate層
└── marts/
├── fct_daily_sales.sql # ← fact(ファクトテーブル)
└── dim_customers.sql # ← dimension(ディメンションテーブル)
- 責務が明確
- 依存関係を追いやすい
- 新メンバーがすぐ理解できる
プレフィックスの判断マトリクス(参考)

図3: stg_/int_/fct_/dim_の比較表。各プレフィックスの責務、マテリアライゼーション、テスト戦略、依存関係を整理。例:stg_=view, 基本テストのみ。
| プレフィックス | 責務 | マテリアライゼーション | テスト戦略 | 依存関係 | 例 |
|---|---|---|---|---|---|
| stg_ | ソースに近い最小変換(リネーム・型変換) | view | not_null, unique(主キーのみ) | source → stg_ |
stg_orders, stg_customers
|
| int_ | staging同士の結合・中間ロジック | view or table(重い場合) | relationships, dbt_utils.expression_is_true | stg_ → int_ | int_orders__with_customer |
| fct_ | ファクトテーブル(イベント・トランザクション) | table or incremental | 全種類(not_null, unique, dbt_expectations) | int_ → fct_ |
fct_daily_sales, fct_customer_orders
|
| dim_ | ディメンションテーブル(マスタ・属性) | table or incremental(SCD Type 2の場合) | not_null, unique, accepted_values | stg_ → dim_ |
dim_customers, dim_products
|
プレフィックスの選定フローチャート
質問1: このモデルはどこから来たか?
-
ソースから直接 →
stg_ -
staging同士の結合 →
int_orfct_/dim_ -
intermediate同士の結合 →
fct_/dim_
質問2: このモデルの粒度は?
-
1行 = 1イベント/トランザクション →
fct_(fact) -
1行 = 1マスタレコード →
dim_(dimension) -
中間集計・結合 →
int_
質問3: このモデルは最終的にBIで使われるか?
-
Yes →
fct_/dim_(martsレイヤー) -
No →
int_(中間レイヤー)
実装例:TPCH_SF1のモデルを命名規約に沿って整理
第5弾で作成したモデルを、命名規約に沿って整理し直します。
Before(第5弾の構成):
models/
├── staging/
│ ├── stg_tpch__customer.sql # ✅ すでに命名規約に準拠
│ ├── stg_tpch__orders.sql
│ └── stg_tpch__lineitem.sql
├── intermediate/
│ └── int_orders__with_customer.sql # ✅ すでに準拠
└── marts/
└── fct_customer_orders.sql # ✅ すでに準拠
第5弾の構成はすでに命名規約に従っています。これをさらに改善します。
After(チーム運用向けに改善):
models/
├── staging/
│ └── tpch/ # ← ソースごとにフォルダを分ける
│ ├── _tpch__models.yml # ← メタデータを集約
│ ├── _tpch__sources.yml # ← ソース定義を集約
│ ├── stg_tpch__customer.sql
│ ├── stg_tpch__orders.sql
│ ├── stg_tpch__lineitem.sql
│ ├── stg_tpch__nation.sql # ← 新規追加(ディメンションで使用)
│ └── stg_tpch__region.sql # ← 新規追加
├── intermediate/
│ └── orders/ # ← ドメインごとにフォルダを分ける
│ ├── _orders__models.yml
│ ├── int_orders__with_customer.sql
│ └── int_orders__with_items.sql # ← 新規追加(行アイテム情報を含む)
└── marts/
├── sales/ # ← ビジネスドメインごとに分ける
│ ├── _sales__models.yml
│ ├── fct_daily_sales.sql # ← 新規追加(日次売上ファクト)
│ └── fct_customer_orders.sql
└── core/ # ← コアディメンション
├── _core__models.yml
├── dim_customers.sql # ← 新規追加(顧客ディメンション)
└── dim_regions.sql # ← 新規追加(地域ディメンション)
改善ポイント:
-
ソースごとにフォルダを分ける(
staging/tpch/)- 複数のソース(例:tpch, salesforce, google_analytics)を扱う場合に整理しやすい
-
ドメインごとにフォルダを分ける(
intermediate/orders/,marts/sales/)- ビジネスドメイン(注文、顧客、商品など)で分類
- 責務の明確化
-
メタデータファイルを集約(
_tpch__models.yml,_orders__models.yml)- 各フォルダに1つのYAMLファイルを配置
- description、tests、metaをまとめて管理
-
ディメンションテーブルを追加(
dim_customers.sql,dim_regions.sql)- ファクトテーブルとディメンションテーブルを明確に分離
- BIツールでのスタースキーマ設計に対応
命名規約のアンチパターン
| アンチパターン | 問題 | 改善案 |
|---|---|---|
staging_orders |
プレフィックスが長い | stg_orders |
orders_staging |
サフィックスは見落としやすい | stg_orders |
orders |
レイヤーが不明 |
stg_orders or fct_orders
|
tbl_orders |
「テーブル」を意味するプレフィックスは責務を表さない | fct_orders |
v_orders |
「ビュー」を意味するプレフィックスはマテリアライゼーションを表すだけ | stg_orders |
フォルダ構成のベストプラクティス(DDD的アプローチ)
dbtプロジェクトのフォルダ構成には、以下の2つのアプローチがあります:
アプローチ1: レイヤー中心(従来型)
models/
├── staging/
│ ├── stg_orders.sql
│ ├── stg_customers.sql
│ └── stg_products.sql
├── intermediate/
│ ├── int_orders__with_customer.sql
│ └── int_orders__with_items.sql
└── marts/
├── fct_daily_sales.sql
└── dim_customers.sql
メリット:
- シンプルで分かりやすい
- dbt初学者でも理解しやすい
デメリット:
- モデル数が増えると、フォルダ内が肥大化
- ドメインが混在し、探しにくい
アプローチ2: ドメイン中心(DDD的)
models/
├── staging/
│ ├── tpch/ # ← ソースごと
│ │ ├── stg_tpch__orders.sql
│ │ └── stg_tpch__customers.sql
│ └── salesforce/
│ └── stg_sf__accounts.sql
├── intermediate/
│ ├── orders/ # ← ビジネスドメインごと
│ │ └── int_orders__with_customer.sql
│ └── products/
│ └── int_products__with_categories.sql
└── marts/
├── sales/ # ← ビジネスドメインごと
│ ├── fct_daily_sales.sql
│ └── fct_customer_orders.sql
└── core/
├── dim_customers.sql
└── dim_products.sql
メリット:
- ビジネスドメインで分類され、探しやすい
- チームがドメイン別に分かれている場合、責務が明確
- スケールしやすい
デメリット:
- 初期構築時にドメイン設計が必要
- 過度に細分化すると逆に複雑になる
推奨構成(ハイブリッドアプローチ)
実務では、レイヤー中心をベースに、ドメインでサブディレクトリを切るハイブリッドアプローチが推奨されます。
models/
├── staging/
│ ├── tpch/ # ← ソースごとにフォルダ
│ │ ├── _tpch__sources.yml
│ │ ├── _tpch__models.yml
│ │ ├── stg_tpch__orders.sql
│ │ ├── stg_tpch__customers.sql
│ │ └── stg_tpch__lineitem.sql
│ └── salesforce/
│ ├── _sf__sources.yml
│ ├── _sf__models.yml
│ └── stg_sf__accounts.sql
├── intermediate/
│ ├── orders/ # ← ドメインごとにフォルダ
│ │ ├── _orders__models.yml
│ │ ├── int_orders__with_customer.sql
│ │ └── int_orders__with_items.sql
│ └── products/
│ ├── _products__models.yml
│ └── int_products__with_categories.sql
└── marts/
├── sales/ # ← ビジネスドメインごと
│ ├── _sales__models.yml
│ ├── fct_daily_sales.sql
│ └── fct_customer_orders.sql
└── core/
├── _core__models.yml
├── dim_customers.sql
└── dim_products.sql
判断基準:
- stagingは「ソース」で分ける(tpch, salesforce, google_analyticsなど)
- intermediateは「ドメイン」で分ける(orders, products, customersなど)
- martsは「ビジネスユースケース」で分ける(sales, marketing, finance, coreなど)
メタデータ管理(schema.yml/sources.yml)
メタデータファイル(schema.yml/sources.yml)の配置方法も重要です。
アンチパターン:すべてを1つのファイルに集約
models/
├── staging/
│ ├── schema.yml # ← すべてのstagingモデルを1ファイルに
│ ├── stg_orders.sql
│ ├── stg_customers.sql
│ └── stg_products.sql
└── ...
問題:
- ファイルが肥大化し、編集しづらい
- 複数人が同時編集するとGit conflictが頻発
ベストプラクティス:フォルダごとに分割
models/
├── staging/
│ ├── tpch/
│ │ ├── _tpch__sources.yml # ← sources専用ファイル
│ │ ├── _tpch__models.yml # ← models専用ファイル
│ │ ├── stg_tpch__orders.sql
│ │ └── stg_tpch__customers.sql
│ └── salesforce/
│ ├── _sf__sources.yml
│ ├── _sf__models.yml
│ └── stg_sf__accounts.sql
└── ...
メリット:
- 責務が明確(ソース定義とモデル定義を分離)
- Git conflictが減る
- フォルダ単位で管理しやすい
ファイル命名規約
| ファイル名 | 用途 | 例 |
|---|---|---|
_<domain>__sources.yml |
ソース定義 | _tpch__sources.yml |
_<domain>__models.yml |
モデル定義(description, tests) | _tpch__models.yml |
命名ルール:
- 先頭に
_を付けることで、モデルSQLではなく「properties(定義)ファイル」だと一目でわかるようにする(dbtはYAMLを読み込み、テストやドキュメント生成に利用します) -
<domain>__で対象ドメインを明確化
Part 3: チーム開発の運用ルール
ブランチ戦略・PR規約
ブランチ戦略
dbtプロジェクトでは、Git Flow簡略版(mainブランチ + featureブランチ)を推奨します。
main (本番環境へデプロイされる)
├── feature/add-dim-customers # ← 機能追加
├── refactor/rename-stg-layer # ← リファクタリング(例)
└── fix/null-handling # ← バグ修正
ブランチ命名規約:
| プレフィックス | 用途 | 例 |
|---|---|---|
feature/ |
新機能追加 | feature/add-dim-customers |
fix/ |
バグ修正 | fix/null-handling |
refactor/ |
リファクタリング | refactor/rename-stg-layer |
docs/ |
ドキュメント追加・修正 | docs/update-readme |
test/ |
テスト追加・修正 | test/add-dbt-expectations |
PR規約(Pull Request Description テンプレート)
PRを作成する際は、以下のテンプレートを使用します。
.github/pull_request_template.md:
## Why(なぜこの変更が必要か)
<!-- 背景・課題・ビジネス価値を記載 -->
- 課題:顧客分析ダッシュボードで地域別売上が見られない
- 目的:地域ディメンションを追加し、地域別分析を可能にする
## What(何を変更したか)
<!-- 変更内容を箇条書き -->
- [ ] 新規モデル追加:`dim_regions.sql`
- [ ] テスト追加:not_null, unique
- [ ] description追加
- [ ] 依存関係の更新:`fct_daily_sales.sql` → `dim_regions.sql`
## Test(どうテストしたか)
<!-- ローカルでの動作確認内容 -->
- [x] `dbt build --select dim_regions` 成功
- [x] `dbt docs generate` でlineage確認
- [x] Snowflakeで実データ確認(レコード数:5件)
## Impact(影響範囲)
<!-- 下流への影響を記載 -->
- **破壊的変更**:なし
- **影響するモデル**:`fct_daily_sales.sql`(新規参照のみ、既存ロジックは変更なし)
- **Lineage**:添付参照(スクリーンショット)
## Checklist
- [x] description追加済み
- [x] テスト追加済み
- [x] ローカルでdbt build成功
- [x] docs/lineageで影響範囲確認
- [ ] レビュー完了
テンプレートの設置方法:
# .github/pull_request_template.md を作成
mkdir -p .github
cat > .github/pull_request_template.md << 'EOF'
## Why(なぜこの変更が必要か)
<!-- 背景・課題・ビジネス価値を記載 -->
## What(何を変更したか)
<!-- 変更内容を箇条書き -->
- [ ] 新規モデル追加
- [ ] テスト追加
- [ ] description追加
## Test(どうテストしたか)
<!-- ローカルでの動作確認内容 -->
- [ ] `dbt build --select <model>` 成功
- [ ] `dbt docs generate` でlineage確認
## Impact(影響範囲)
<!-- 下流への影響を記載 -->
- **破壊的変更**:あり/なし
- **影響するモデル**:
- **Lineage**:
## Checklist
- [ ] description追加済み
- [ ] テスト追加済み
- [ ] ローカルでdbt build成功
- [ ] docs/lineageで影響範囲確認
- [ ] レビュー完了
EOF
レビューチェックリスト(dbt特有の観点)
PRレビュー時に確認すべき観点をチェックリストにまとめます。
レビューチェックリスト(Markdown版)
.github/dbt_pr_checklist.md:
# dbtモデル変更のPRレビューチェックリスト
## 1. コード品質
- [ ] **SQLがdbtベストプラクティスに従っているか**
- CTEを使っているか(サブクエリではなく)
- `ref()` / `source()` を使っているか(ハードコードしていないか)
- 集計関数の使い方は適切か(GROUP BY、HAVING)
- [ ] **ハードコード値がないか**
- config / var / env_var を使っているか
- 日付や閾値を直接書いていないか
- [ ] **命名規約に従っているか**
- stg_/int_/fct_/dim_のプレフィックスは正しいか
- ファイル名とモデル名が一致しているか
## 2. ドキュメント
- [ ] **モデルのdescriptionがあるか**
- 目的・データ粒度・更新頻度が記載されているか
- ビジネスロジックが説明されているか
- [ ] **新規カラムにdescriptionがあるか**
- 計算式・単位・注意点が記載されているか
- [ ] **ビジネスロジックの変更がdescriptionに反映されているか**
## 3. テスト
- [ ] **主キーに unique + not_null があるか**
- [ ] **外部キーに relationships があるか**
- [ ] **重要なビジネスルールのsingular testがあるか**
- [ ] **dbt-expectationsでデータ品質テストが追加されているか**(該当する場合)
## 4. 影響範囲
- [ ] **Lineage Graphで下流への影響を確認したか**
- dbt docs generateでlineage確認
- スクリーンショットをPRに添付
- [ ] **破壊的変更(カラム削除・型変更)がないか**
- ある場合、段階的移行の計画はあるか
- [ ] **依存モデルのテストが通るか**
- `dbt build --select +<model>+` で依存関係含めてビルド確認
## 5. パフォーマンス
- [ ] **マテリアライゼーションは適切か**
- staging: view
- intermediate: view or table(重い場合)
- marts: table or incremental
- [ ] **incrementalの場合、unique_keyと更新戦略は正しいか**
- [ ] **不要なJOINやサブクエリがないか**
## 6. セキュリティ・コンプライアンス
- [ ] **個人情報(PII)を含むカラムにmetaタグがあるか**
- `meta: { pii: true }`
- [ ] **機密情報がハードコードされていないか**
## 7. CI/CD
- [ ] **CIが成功しているか**
- GitHub Actions / dbt Cloudのチェックが通っているか
- [ ] **CI失敗時のログを確認したか**
活用方法:
- PRレビュー時にこのチェックリストを参照
- チーム内でレビュー観点を統一
- 見落としが減る
スキーマ戦略(開発/CI/本番環境)
dbtプロジェクトをチームで運用する際、環境ごとにスキーマを分離することが重要です。

図4: スキーマ戦略の全体図。開発/CI/本番のSchema分離イメージ。上段:dev(例: DBT_DEV_alice)、中段:ci(例: DBT_PR_123)、下段:prod(例: DBT_PROD もしくは RAW/CORE/MART)。矢印で環境分離を示す。
スキーマ戦略の3つの環境
| 環境 | スキーマ名 | 用途 | 誰が使うか | データの鮮度 |
|---|---|---|---|---|
| 開発(dev) | DBT_DEV_<ユーザー名> |
ローカル開発・試行錯誤 | 各開発者 | 古くてもOK |
| CI | DBT_PR_<PR番号> |
PR検証(一時) | GitHub Actions | PR作成時に生成・削除 |
| 本番(prod) |
RAW/CORE/MART(レイヤー別)※ target.schema はfallback |
本番データ | BIツール・アプリ | 常に最新 |
profiles.ymlでの環境分離
第7弾で構築した profiles.yml.template を拡張します。
claude_dbt_demo:
target: dev
outputs:
dev:
type: snowflake
account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}"
user: "{{ env_var('SNOWFLAKE_USER') }}"
role: "{{ env_var('SNOWFLAKE_ROLE') }}"
warehouse: "{{ env_var('SNOWFLAKE_WAREHOUSE') }}"
database: "{{ env_var('SNOWFLAKE_DATABASE') }}"
schema: "DBT_DEV_{{ env_var('USER', env_var('USERNAME', 'default')) }}"
authenticator: snowflake_jwt
private_key_path: "{{ env_var('SNOWFLAKE_PRIVATE_KEY_PATH', '/tmp/snowflake_key.p8') }}"
threads: 4
ci:
type: snowflake
account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}"
user: "{{ env_var('SNOWFLAKE_USER') }}"
role: "{{ env_var('SNOWFLAKE_ROLE') }}"
warehouse: "{{ env_var('SNOWFLAKE_WAREHOUSE') }}"
database: "{{ env_var('SNOWFLAKE_DATABASE') }}"
schema: "{{ env_var('DBT_SCHEMA', 'DBT_CI') }}" # ← PR番号で動的に変更
authenticator: snowflake_jwt
private_key_path: "{{ env_var('SNOWFLAKE_PRIVATE_KEY_PATH', '/tmp/snowflake_key.p8') }}"
threads: 4
prod:
type: snowflake
account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}"
user: "{{ env_var('SNOWFLAKE_USER') }}"
role: "{{ env_var('SNOWFLAKE_ROLE') }}"
warehouse: "{{ env_var('SNOWFLAKE_WAREHOUSE') }}"
database: "{{ env_var('SNOWFLAKE_DATABASE') }}"
schema: DBT_PROD # fallback(+schema がない場合に使われる)
authenticator: snowflake_jwt
private_key_path: "{{ env_var('SNOWFLAKE_PRIVATE_KEY_PATH', '/tmp/snowflake_key.p8') }}"
threads: 8
CIでPR番号スキーマを指定する例(GitHub Actions):
# .github/workflows/dbt_ci.yml(抜粋)
env:
DBT_TARGET: ci
DBT_SCHEMA: DBT_PR_${{ github.event.pull_request.number }}
macros/generate_schema_name.sql:
{% macro generate_schema_name(custom_schema_name, node) -%}
{%- set default_schema = target.schema -%}
{%- if target.name == 'prod' and custom_schema_name is not none -%}
{#- 本番環境: カスタムスキーマ名をそのまま使用(例: RAW / CORE / MART / sales など) -#}
{{ custom_schema_name | trim }}
{%- elif target.name == 'ci' -%}
{#- CI環境: すべてのモデルを1つのスキーマに統合(DBT_PR_123) -#}
{{ default_schema }}
{%- else -%}
{#- 開発環境: すべてのモデルを1つのスキーマに統合(DBT_DEV_username) -#}
{{ default_schema }}
{%- endif -%}
{%- endmacro %}
動作例:
| 環境 | target.name | target.schema | 実際のスキーマ |
|---|---|---|---|
| 開発 | dev |
DBT_DEV_alice |
DBT_DEV_alice |
| CI | ci |
DBT_PR_123 |
DBT_PR_123 |
| 本番 | prod |
DBT_PROD |
DBT_PROD(custom_schema_nameなし) / <custom_schema_name>(あり) |
参考:第7弾CI/CD記事でも同様の実装を紹介しています。
💡 Tips: スキーマが
PUBLIC_RAW/PUBLIC_CORE/PUBLIC_MARTになるのはなぜ?(dbtのデフォルト命名規則)Snowflake × dbt で「RAW/CORE/MART のようにレイヤー別スキーマを作りたい」と設定したのに、
PUBLIC_RAWのように 意図せずプレフィックスが付いたスキーマができて困るケースがあります。これは dbtの仕様(デフォルトのスキーマ生成ロジック) なので、原因を理解してから設計を選ぶのが安全です。
なぜ PUBLIC_RAW のように結合される?
dbtは profiles.yml の schema(= target.schema)と、モデル側で指定した schema(= custom_schema_name)が異なる場合、
デフォルトでは {default_schema}_{model_schema} の形式でスキーマ名を結合します。
target.schema = PUBLICcustom_schema_name = RAW
→ 結果として PUBLIC_RAW が作成されます。
この挙動は、Snowflakeのマルチユーザー環境(複数人が同じDBを触る)で
RAW のような同名スキーマが衝突しないようにする意図があります。
対策: prod だけ結合をやめる(dev/ci は統合のまま)
今回の設計(dev/ci は「1人/1PR = 1スキーマ」で隔離)なら、prod だけ RAW / CORE / MART のようなスキーマ名をそのまま使うのが分かりやすいです。
- dev:
DBT_DEV_<user>(個人の検証場所) - ci:
DBT_PR_<PR番号>(PRごとの一時領域) - prod:
RAW / CORE / MART(レイヤー別にする場合)
このとき、dbtのデフォルト挙動だと PUBLIC_RAW のように <target_schema>_<custom_schema> で結合されてしまいます。上で紹介した macros/generate_schema_name.sql を使えば、prod のときだけ custom schema をそのまま返すようにでき、PUBLIC_RAW を回避できます(詳細はdbt公式の Custom schemas を参照)。
schema はどこで指定する?(推奨はフォルダ単位)
スキーマ指定は、基本的には dbt_project.yml でフォルダ(モデルグループ)単位にまとめるのが運用しやすいです。
models:
claude_dbt_demo: # ← `name:` と同じ。自分のプロジェクト名に置き換えてください
staging:
+materialized: view
+schema: RAW
intermediate:
+materialized: view
+schema: CORE
marts:
+materialized: table
+schema: MART
個別モデルで明示することもできますが、スキーマ変更時のメンテが大変になるので、例外扱いがおすすめです。
{{ config(
materialized='view',
schema='RAW'
) }}
select ...
学び(このTipsの結論)
-
PUBLIC_RAWは dbtのデフォルト動作(target.schemaとcustom_schema_nameの結合) -
レイヤー別スキーマ(RAW/CORE/MART)を"そのまま"使いたいなら、
generate_schema_nameを上書きする - ただし チーム開発では衝突回避もセットで設計する(環境分離・スキーマ命名規約・権限設計など)
参考:
- dbt公式: Custom schemas
- 解説記事(Classmethod)
スキーマ戦略のメリット
- 開発環境の隔離:各開発者が独自のスキーマで作業でき、他のメンバーに影響しない
- CI環境の並列実行:PR番号ごとにスキーマが分かれるため、複数PRが同時にCIを実行しても競合しない
- 本番環境の保護:開発者が誤って本番スキーマを直接変更できないようにする
Part 4: 運用保守の勝ちパターン
リファクタリング戦略(モデル名変更・フォルダ再編成)
dbtプロジェクトを長期運用すると、「初期の命名が曖昧だった」「フォルダ構成を見直したい」といった理由でリファクタリングが必要になります。
リファクタリングの典型パターン
| パターン | 例 | 影響範囲 |
|---|---|---|
| モデル名変更 |
orders_staging → stg_orders
|
下流モデルの ref() を全修正 |
| フォルダ移動 |
models/stg_orders.sql → models/staging/tpch/stg_tpch__orders.sql
|
ref() は変わらないが、パスが変わる |
| カラムリネーム |
customer_id → customer_key
|
下流モデルのSELECTを全修正 |
リファクタリングの手順(モデル名変更の例)
シナリオ:orders_staging → stg_orders に変更したい
手順1: 影響範囲の確認
# 依存モデルを確認
dbt ls --select +orders_staging+
# 出力例:
# source:claude_dbt_demo.tpch.orders
# claude_dbt_demo.orders_staging
# claude_dbt_demo.int_orders__with_customer
# claude_dbt_demo.fct_customer_orders
手順2: 下流モデルの ref() を一括置換
# Grepで依存モデルを探す
grep -r "ref('orders_staging')" models/
# 出力例:
# models/intermediate/int_orders__with_customer.sql:from {{ ref('orders_staging') }}
# models/marts/fct_customer_orders.sql:from {{ ref('orders_staging') }}
# 一括置換(sedの例)
find models/ -type f -name "*.sql" -exec sed -i "s/ref('orders_staging')/ref('stg_orders')/g" {} +
※ macOS(BSD sed)では sed -i '' のようにバックアップ拡張子が必要です。Linux(GNU sed)と挙動が違うので注意してください。
手順3: ファイル名変更
# ファイル名をリネーム
mv models/staging/orders_staging.sql models/staging/stg_orders.sql
手順4: schema.ymlのモデル名を更新
# models/staging/_staging__models.yml
models:
- name: stg_orders # ← orders_staging から変更
description: 注文データ(ステージング層)
columns:
- name: order_key
tests:
- unique
- not_null
手順5: 動作確認
# 依存関係含めてビルド
dbt build --select +stg_orders+
# docs生成でlineage確認
dbt docs generate
dbt docs serve
手順6: 本番環境での移行
# 旧モデルを削除する前に、新モデルをデプロイ
dbt run --select stg_orders --target prod
# 旧モデルを削除(手動でSnowflakeから削除)
# DROP VIEW IF EXISTS <DATABASE>.RAW.orders_staging; -- 例: stagingをRAWスキーマに出している場合
手順7: コミット・PR
git add -A
git commit -m "Refactor: rename orders_staging to stg_orders"
git push origin refactor/rename-stg-layer
フォルダ再編成の例
第5弾のフォルダ構成を、チーム運用向けに再編成します。
Before:
models/
├── staging/
│ ├── stg_tpch__customer.sql
│ ├── stg_tpch__orders.sql
│ └── stg_tpch__lineitem.sql
├── intermediate/
│ └── int_orders__with_customer.sql
└── marts/
└── fct_customer_orders.sql
After:
models/
├── staging/
│ └── tpch/
│ ├── _tpch__sources.yml
│ ├── _tpch__models.yml
│ ├── stg_tpch__customer.sql
│ ├── stg_tpch__orders.sql
│ └── stg_tpch__lineitem.sql
├── intermediate/
│ └── orders/
│ ├── _orders__models.yml
│ └── int_orders__with_customer.sql
└── marts/
└── sales/
├── _sales__models.yml
└── fct_customer_orders.sql
移行手順:
# 1. フォルダ作成
mkdir -p models/staging/tpch
mkdir -p models/intermediate/orders
mkdir -p models/marts/sales
# 2. ファイル移動
mv models/staging/stg_tpch__*.sql models/staging/tpch/
mv models/intermediate/int_orders__*.sql models/intermediate/orders/
mv models/marts/fct_*.sql models/marts/sales/
# 3. schema.yml移動
mv models/staging/_staging__models.yml models/staging/tpch/_tpch__models.yml
# (同様に他のymlも移動)
# 4. 動作確認
dbt build
破壊的変更の対処(カラム削除・リネーム)
破壊的変更(カラム削除、型変更、リネーム)は、下流に影響するため慎重な対応が必要です。

図5: 破壊的変更の段階的移行フロー(3状態)。「旧カラムのみ(現状)」→「旧+新併存(移行期間)」→「新カラムのみ(完了)」の順に進め、下流(モデル/BI/アプリ)を段階的に切り替えます。矢印の「2週間」は例です。
段階的移行は「3つの状態」で考える
シナリオ:customer_id → customer_key にリネームしたい
破壊的変更(カラム削除 / リネーム / 型変更)は、いきなり切り替えると下流(モデル / BI / アプリ)が壊れます。
図5のように 「旧のみ → 旧+新併存 → 新のみ」 の3状態で移行すると安全です。
状態1: 旧カラムのみ(現状)
- まず「どこで使われているか」を把握します(dbt docsのLineage /
dbt ls/grepなど)。 - BIやアプリの依存は Exposures に紐付けておくと、影響範囲が「モデルの外」まで見えるようになります。
# 例: customer_id を参照しているSQLモデルを洗い出す
find models -type f -name "*.sql" -print0 | xargs -0 grep -n "customer_id"
※
xargs -0はGNU/LinuxとmacOSで使えます。Windowsの場合はWSLやGit Bashを推奨します。
状態2: 旧+新併存(移行期間: 例 2週間)
- stagingで新カラムを追加しつつ、旧カラムを残して互換性を維持します。
-- models/staging/stg_tpch__customer.sql
select
c_custkey as customer_key, -- ← 新カラム
c_custkey as customer_id, -- ← 旧カラム(互換性のため残す)
c_name as customer_name,
c_address as customer_address
from {{ source('tpch', 'customer') }}
- properties(
schema.yml)に「非推奨」を明記します。
metaは自由形式のメタ情報で、dbtが自動で「deprecated」を解釈して何かをしてくれるわけではありません(docs/lineageや運用ツール向けの情報として使います)。
また、dbt v1.10以降は meta / tags を config: 配下に寄せる形式が推奨されています(後方互換で top-level でも動く場合がありますが、混乱を避けるため本記事では config: meta: に統一します)。
models:
- name: stg_tpch__customer
columns:
- name: customer_id
description: |
**【非推奨】customer_idは廃止予定です。customer_keyを使用してください。**
削除予定日(例):2026-02-01
config:
meta:
deprecated: true
replacement: customer_key
- name: customer_key
description: |
**顧客キー**(主キー)
customer_idから移行。今後はこちらを使用してください。
- 互換期間中に、下流モデルを順次
customer_keyに移行します(PRを小さく刻むのが安全です)。
-- models/intermediate/int_orders__with_customer.sql(Before)
select
orders.order_key,
orders.customer_id, -- ← 旧カラムを使用
customers.customer_name
from {{ ref('stg_tpch__orders') }} as orders
left join {{ ref('stg_tpch__customer') }} as customers
on orders.customer_id = customers.customer_id
-- models/intermediate/int_orders__with_customer.sql(After)
select
orders.order_key,
orders.customer_key, -- ← 新カラムに移行
customers.customer_name
from {{ ref('stg_tpch__orders') }} as orders
left join {{ ref('stg_tpch__customer') }} as customers
on orders.customer_key = customers.customer_key
移行確認:
# 旧カラム参照が残っていないか確認(SQLファイルだけ検索)
find models -type f -name "*.sql" -print0 | xargs -0 grep -n "customer_id"
# 出力がなければ移行完了(= customer_id がSQL上で参照されていない)
状態3: 新カラムのみ(完了)
下流移行が完了したら、旧カラムを削除します。
-- models/staging/stg_tpch__customer.sql
select
c_custkey as customer_key, -- ← 新カラムのみ残す
c_name as customer_name,
c_address as customer_address
from {{ source('tpch', 'customer') }}
schema.ymlから旧カラムを削除:
models:
- name: stg_tpch__customer
columns:
- name: customer_key
description: 顧客キー(主キー)
tests:
- unique
- not_null
破壊的変更のチェックリスト
# 破壊的変更のチェックリスト
## 変更前
- [ ] 影響範囲の確認(dbt docs lineage)
- [ ] 下流モデルのリストアップ
- [ ] 移行計画の策定(3状態。期間はチーム事情に合わせて調整)
## 状態2: 旧+新併存(互換期間)
- [ ] 新カラム追加
- [ ] 旧カラムに「非推奨」を明記(description / meta)
- [ ] PR作成・レビュー
- [ ] 本番デプロイ
## 状態2の間にやること: 下流移行
- [ ] 下流モデルを順次修正
- [ ] 各PR作成・レビュー
- [ ] 本番デプロイ(段階的)
## 状態3: 旧カラム削除
- [ ] 旧カラムを使用していないことを確認(grep検索)
- [ ] 旧カラム削除
- [ ] PR作成・レビュー
- [ ] 本番デプロイ
移行・統合シナリオ
複数のソースを統合するシナリオでも、段階的移行が有効です。
シナリオ:SalesforceとZendeskの顧客データを統合した dim_customers_unified を作りたい
ステップ1: 既存モデルはそのまま残す
models/
├── staging/
│ ├── salesforce/
│ │ └── stg_sf__accounts.sql
│ └── zendesk/
│ └── stg_zd__users.sql
└── marts/
└── core/
├── dim_customers_sf.sql # ← Salesforce専用(既存)
└── dim_customers_zd.sql # ← Zendesk専用(既存)
ステップ2: 統合モデルを追加
-- models/marts/core/dim_customers_unified.sql
with sf_customers as (
select
account_id as customer_key,
'salesforce' as source_system,
account_name as customer_name,
industry,
created_date
from {{ ref('stg_sf__accounts') }}
),
zd_customers as (
select
user_id as customer_key,
'zendesk' as source_system,
user_name as customer_name,
null as industry,
created_at as created_date
from {{ ref('stg_zd__users') }}
)
select * from sf_customers
union all
select * from zd_customers
ステップ3: 下流モデルを段階的に移行
-- models/marts/sales/fct_customer_lifetime_value.sql(Before)
select
customer_key,
customer_name,
sum(order_amount) as lifetime_value
from {{ ref('dim_customers_sf') }} -- ← Salesforce専用
left join {{ ref('fct_orders') }} using (customer_key)
group by 1, 2
-- models/marts/sales/fct_customer_lifetime_value.sql(After)
select
customer_key,
customer_name,
source_system,
sum(order_amount) as lifetime_value
from {{ ref('dim_customers_unified') }} -- ← 統合モデルに移行
left join {{ ref('fct_orders') }} using (customer_key)
group by 1, 2, 3
ステップ4: 旧モデルを廃止
-- models/marts/core/dim_customers_sf.sql に「非推奨」メタ情報を追加
{{
config(
materialized='view',
meta={'deprecated': true, 'replacement': 'dim_customers_unified'}
)
}}
select * from {{ ref('dim_customers_unified') }}
where source_system = 'salesforce'
Part 5: パフォーマンス・コスト管理
パフォーマンス最適化(incremental・クラスタリング)
incrementalの選定基準
| モデルの特性 | materialization | 理由 |
|---|---|---|
| 行数 < 100万 | table | フルリフレッシュでも高速 |
| 行数 > 100万 | incremental | 差分更新でコスト削減 |
| 更新頻度:高(時間ごと) | incremental | フルリフレッシュは非現実的 |
| 更新頻度:低(週次) | table | フルリフレッシュで単純化 |
incrementalの実装例
-- models/marts/sales/fct_daily_sales.sql
{{
config(
materialized='incremental',
unique_key='sales_date',
on_schema_change='append_new_columns'
)
}}
select
date_trunc('day', order_date) as sales_date,
sum(total_price) as total_revenue,
count(distinct order_key) as order_count,
current_timestamp() as _dbt_updated_at
from {{ ref('stg_tpch__orders') }}
{% if is_incremental() %}
-- 直近7日間のみ再集計
where order_date >= dateadd(day, -7, current_date())
{% endif %}
group by 1
ポイント:
-
unique_key='sales_date':既存レコードを上書き -
is_incremental():初回フルリフレッシュ、2回目以降は差分更新 -
_dbt_updated_at:更新日時を記録(トラブルシューティングに有用)
クラスタリングの活用
Snowflakeでは、クラスタリングキーを設定することで検索性能を向上できます。
-- models/marts/sales/fct_customer_orders.sql
{{
config(
materialized='incremental',
unique_key=['customer_key', 'region_key'], # 複合キー(customer_key 単体だと重複する可能性あり)
cluster_by=['customer_key', 'region_key']
)
}}
select
customer_key,
region_key,
sum(total_price) as lifetime_value,
count(order_key) as total_orders
from {{ ref('int_orders__with_customer') }}
{% if is_incremental() %}
where order_date >= dateadd(day, -7, current_date())
{% endif %}
group by 1, 2
※ Snowflakeで incremental_strategy='merge' を使う場合、unique_key がユニークでないと「nondeterministic merge」エラーになることがあります。複合キーにするか、delete+insert 戦略を検討してください。
クラスタリングキーの選定基準:
- WHERE句で頻繁にフィルタされるカラム
- JOIN条件で使われるカラム
- カーディナリティが高いカラム(例:customer_key)
コスト管理(Warehouseサイズ・実行頻度)
Warehouseサイズの選定
| 環境 | Warehouseサイズ | 理由 |
|---|---|---|
| 開発(dev) | X-Small | 個人開発、コスト最小化 |
| CI | X-Small or Small | PR検証、並列実行なし |
| 本番(prod) | Small ~ Large | データ量・実行頻度に応じて調整 |
実行頻度の最適化
# dbt_project.yml
models:
claude_dbt_demo:
staging:
+materialized: view # ← ストレージコスト不要、都度クエリ実行
intermediate:
+materialized: view
marts:
sales:
+materialized: table # ← 参照頻度が高い場合はtable
fct_daily_sales:
+materialized: incremental # ← 大規模データはincremental
Warehouse自動停止の設定
-- Snowflakeで実行
ALTER WAREHOUSE DBT_WH SET AUTO_SUSPEND = 60; -- 60秒未使用で自動停止
ALTER WAREHOUSE DBT_WH SET AUTO_RESUME = TRUE; -- クエリ実行時に自動起動
コスト可視化
-- 過去7日間のクレジット消費量を確認
SELECT
DATE_TRUNC('day', start_time) AS query_date,
warehouse_name,
SUM(credits_used) AS total_credits
FROM SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY
WHERE start_time >= DATEADD(day, -7, CURRENT_TIMESTAMP())
GROUP BY 1, 2
ORDER BY 1 DESC, 3 DESC;
Part 6: 第1-8弾の総まとめ+運用Tips
第1-8弾の振り返り
連載を通じて構築した環境をまとめます。
| 弾 | 構築内容 | 主要なアウトプット |
|---|---|---|
| 第1弾 | dbtの全体像 | staging→marts、materialization、ELT理解 |
| 第2弾 | dbt Projects on Snowflake | Snowflakeネイティブ実行環境 |
| 第3弾 | ローカル開発環境 | VS Code + Power User for dbt、profiles.yml |
| 第4弾 | テスト戦略 | schema tests、singular tests、テスト駆動開発 |
| 第5弾 | Claude Code連携 | AI駆動のdbt開発、生産性向上 |
| 第6弾 | docs/lineage | dbt docs、Data Lineage、影響範囲分析 |
| 第7弾 | CI/CD | GitHub Actions、Slim CI、鍵ペア認証 |
| 第8弾 | dbt-expectations | データ品質テスト、統計的テスト |
運用Tips集
実務で使える運用Tipsをまとめます。
1. タグ選択実行で柔軟なビルド
# tier_1(本番重要データ)のみビルド
dbt build --select tag:tier_1
# sales_domainのみビルド
dbt build --select tag:sales_domain
# 本番環境でproductionタグのみフルリフレッシュ
dbt run --select tag:production --full-refresh --target prod
tagsの設定例:
# models/marts/sales/_sales__models.yml
models:
- name: fct_customer_orders
tags:
- mart
- sales_domain
- tier_1
- production
2. 失敗行保存(store_failures)
テスト失敗時に、失敗したレコードをテーブルに保存します。
# dbt_project.yml
tests:
+store_failures: true
+schema: dbt_test_failures
失敗レコードの確認:
-- 例: database = DBT_DB の場合(<DATABASE> は profiles.yml の database と同じ)
SELECT * FROM DBT_DB.dbt_test_failures.unique_stg_orders_order_key;
3. docs高速化(--no-compile)
# 1回目: docs 用のアーティファクト(manifest.json / catalog.json など)を生成
# ※ catalog 生成のために DWH へ接続します
dbt docs generate
# 2回目以降: 再コンパイルをスキップして高速化(既に manifest.json がある前提)
dbt docs generate --no-compile
# DWH に接続できない / catalog を作らない場合(必要最低限の docs)
dbt docs generate --no-compile --empty-catalog
4. マクロでスキーマ操作を共通化
-- macros/schema_operations.sql
{% macro create_schema_if_not_exists(schema_name) %}
{% set sql %}
create schema if not exists {{ target.database }}.{{ schema_name }}
{% endset %}
{% do run_query(sql) %}
{% endmacro %}
{% macro drop_schema_if_exists(schema_name) %}
{% set sql %}
drop schema if exists {{ target.database }}.{{ schema_name }} cascade
{% endset %}
{% do run_query(sql) %}
{% endmacro %}
使用例:
# PRスキーマを作成
dbt run-operation create_schema_if_not_exists --args '{"schema_name": "DBT_PR_123"}'
# PRスキーマを削除
dbt run-operation drop_schema_if_exists --args '{"schema_name": "DBT_PR_123"}'
5. 環境変数でprofiles.ymlを柔軟に
# profiles.yml.template
claude_dbt_demo:
target: "{{ env_var('DBT_TARGET', 'dev') }}" # ← デフォルトは dev
outputs:
dev:
# ...
ci:
# ...
prod:
# ...
使用例:
# 推奨: --target を明示して実行する(環境切り替え事故を防ぐ)
dbt build --target prod
dbt build --target ci
# DBT_TARGET を使う場合は、作業が終わったら必ず元に戻す
export DBT_TARGET=prod
dbt build
unset DBT_TARGET # ← profiles.yml のデフォルト(例: dev)に戻す
6. dbt parse でCI高速化
# main ブランチ側で manifest.json を生成(SQL実行なし)
# CI ではこの manifest.json を state/manifest.json として保存しておく
dbt parse --target prod
# PR ブランチで Slim CI(main の manifest と比較して差分のみ build)
dbt build --select state:modified+ --defer --state state --target ci
CIでの活用(第7弾で実装済み):
# .github/workflows/dbt_ci.yml(抜粋)
- name: Generate manifest from main branch (state)
run: |
cd main-branch
dbt deps
dbt parse --target prod # ← SQL実行なしで高速
cd ..
mkdir -p state
cp main-branch/target/manifest.json state/manifest.json
7. dbt ls でモデル一覧を確認
# モデルだけを一覧表示
dbt ls --resource-type model
# 特定セレクターの依存関係を確認(sources / tests も含まれる)
dbt ls --select "+stg_orders+"
# 出力例:
# source:claude_dbt_demo.tpch.orders
# claude_dbt_demo.stg_orders
# claude_dbt_demo.int_orders__with_customer
# claude_dbt_demo.fct_customer_orders
8. dbt compile で生成SQLを確認
# 特定モデルのコンパイル済みSQLを生成
dbt compile --select fct_customer_orders
# SELECT 部分(コンパイル結果)を確認
cat target/compiled/claude_dbt_demo/models/marts/sales/fct_customer_orders.sql
# dbt run / dbt build 後なら、実行された CREATE 文も確認できる
cat target/run/claude_dbt_demo/models/marts/sales/fct_customer_orders.sql
ハマりどころ(運用編)
1. 命名規約の徹底
症状:stg_ordersとstaging_ordersが混在し、構造が読み解けない
原因:
- チームで命名規約が共有されていない
- レビュー時に命名をチェックしていない
解決策:
- 命名規約をドキュメント化(READMEやCONTRIBUTING.md)
- PRレビューチェックリストに「命名規約に従っているか」を追加
- pre-commitで命名規約チェック(sqlfluff等)
2. スキーマ爆発
症状:開発者ごとに独自スキーマが増殖し、管理不能に
原因:
- 開発環境のスキーマ命名ルールが曖昧
- 使われなくなったスキーマが放置される
解決策:
- スキーマ命名ルールを統一(
DBT_DEV_<ユーザー名>) - 定期的にスキーマを棚卸し(月次で不要なスキーマを削除)
- CIスキーマは自動削除(第7弾で実装済み)
-- 使われていないスキーマを確認
SHOW SCHEMAS IN DATABASE DBT_DB;
-- 不要なスキーマを削除
DROP SCHEMA IF EXISTS DBT_DB.DBT_DEV_olduser CASCADE;
3. 破壊的変更の影響範囲
症状:カラム削除が下流に影響し、ダッシュボードが壊れる
原因:
- 影響範囲を確認せずに変更
- 段階的移行をしていない
解決策:
- dbt docs lineageで影響範囲を確認
- 段階的移行(新旧併存→下流移行→旧カラム削除)
- Exposuresでダッシュボード依存を可視化
# models/exposures.yml
exposures:
- name: customer_ltv_dashboard
type: dashboard
url: https://tableau.example.com/dashboards/customer-ltv
description: 顧客LTVダッシュボード(例)
depends_on:
- ref('fct_customer_orders')
- ref('dim_customers')
owner:
name: data-team
email: data-team@example.com
4. パフォーマンス劣化
症状:dbt run が徐々に遅くなる
原因:
- すべてのモデルがtable materializationで、フルリフレッシュしている
- incrementalに変更すべきモデルを放置
解決策:
- 大規模モデル(行数 > 100万)はincrementalに変更
- クラスタリングキーを設定
- 実行ログでボトルネックを特定(
logs/dbt.log)
# 実行時間を確認
grep "Completed" logs/dbt.log | sort -k 10 -n -r | head -10
まとめ
運用設計の3本柱(再掲)

図1(再掲): 命名規約・フォルダ構成(一貫性)、チーム開発の運用ルール(協働)、運用保守の戦略(持続性)が相互に支え合い、スケールするdbtプロジェクトを実現する。
-
命名規約・フォルダ構成(一貫性)
- stg_/int_/fct_/dim_のプレフィックス
- ドメイン駆動設計的なフォルダ構成
- メタデータ管理(schema.yml/sources.yml)
-
チーム開発の運用ルール(協働)
- ブランチ戦略・PR規約
- レビューチェックリスト
- スキーマ戦略(開発/CI/本番の分離)
-
運用保守の戦略(持続性)
- リファクタリング戦略
- 破壊的変更の段階的移行
- パフォーマンス・コスト管理
今日から適用できる運用ルール
- 命名規約をREADMEに明記
- PR Descriptionテンプレートを設置
- レビューチェックリストを共有
- スキーマ戦略を統一(DBT_DEV_<ユーザー名>、DBT_PR_<PR番号>、DBT_PROD)
- 破壊的変更は段階的移行(新旧併存→移行→削除)
次のステップ
dbt入門シリーズはこれで完結ですが、さらなる発展として以下を検討してください:
- dbt Cloud移行:マネージド環境でCI/CD・スケジューリングを統合
- Data Observability:Monte Carlo、Anomalo、dbt Cloud Discoveryなどで異常検知
- メダリオン(Bronze/Silver/Gold)への移行:Databricks/Delta Lake環境での運用
- Airflow/Dagster連携:dbtを含む複雑なデータパイプラインのオーケストレーション
- dbt Semantic Layer:MetricFlow導入で指標管理を統一
連載を通じて構築したもの
第1-9弾を通じて、以下を構築しました:
- ✅ dbtの全体像理解
- ✅ ローカル開発環境(VS Code + Power User for dbt)
- ✅ テスト戦略(schema tests / singular tests / dbt-expectations)
- ✅ ドキュメント・影響範囲分析(dbt docs / Data Lineage)
- ✅ CI/CD(GitHub Actions + Slim CI)
- ✅ チーム運用設計(命名規約・PR規約・スキーマ戦略・破壊的変更対処)
これで、個人開発から本番運用まで対応できる「スケールするdbtプロジェクト」 が完成しました。
📚 参考リンク
dbt公式ドキュメント
- dbt Best Practices
- How we structure our dbt projects
- Custom schemas
- Refactoring legacy SQL to dbt
- dbt docs generate
- Defer(state / defer の使い方)
- dbt ls(dbt公式コマンドリファレンス)
- SQL models(compiled/run についての説明)
- meta config(dbt公式)
- Snowflake configs(incremental / merge と unique_key の注意)
- Snowflake setup(Key pair authentication)
Discussion