🏗️

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_ordersstaging_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つの柱
図1: 運用設計の3つの柱。命名規約・フォルダ構成(一貫性)、チーム開発の運用ルール(協働)、運用保守の戦略(持続性)が相互に支え合い、スケールするdbtプロジェクトを実現する。

1. 命名規約・フォルダ構成(一貫性)

目的:プロジェクト構造の一貫性を保ち、新メンバーでも迷わない設計にする

要素

  • モデル命名規約(stg_/int_/fct_/dim_のプレフィックス)
  • フォルダ構成(ドメイン駆動設計的なアプローチ)
  • メタデータ管理(schema.yml/sources.yml)

効果

  • 「このファイルどこ?」が激減
  • モデルの責務が明確になる
  • 新メンバーのオンボーディング時間が短縮

2. チーム開発の運用ルール(協働)

目的:複数人が並行して開発できる環境を作り、品質を担保する

要素

  • ブランチ戦略・PR規約
  • レビューチェックリスト(dbt特有の観点)
  • スキーマ戦略(開発/CI/本番環境の分離)

効果

  • 本番環境の汚染防止
  • レビュー観点の標準化
  • 並行開発時の競合回避

3. 運用保守の戦略(持続性)

目的:プロジェクトを長期的に保守・改善できる仕組みを作る

要素

  • リファクタリング戦略(モデル名変更・フォルダ再編成)
  • 破壊的変更の対処(カラム削除・リネーム)
  • パフォーマンス・コスト管理

効果

  • 技術的負債の抑制
  • 破壊的変更時の影響範囲を制御
  • 運用コストの最適化

第1-8弾で構築した環境の棚卸し

図2: 第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: 命名規約の判断マトリクス
図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_ or fct_/dim_
  • intermediate同士の結合fct_/dim_

質問2: このモデルの粒度は?

  • 1行 = 1イベント/トランザクションfct_(fact)
  • 1行 = 1マスタレコードdim_(dimension)
  • 中間集計・結合int_

質問3: このモデルは最終的にBIで使われるか?

  • Yesfct_/dim_(martsレイヤー)
  • Noint_(中間レイヤー)

実装例: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        # ← 新規追加(地域ディメンション)

改善ポイント

  1. ソースごとにフォルダを分けるstaging/tpch/

    • 複数のソース(例:tpch, salesforce, google_analytics)を扱う場合に整理しやすい
  2. ドメインごとにフォルダを分けるintermediate/orders/, marts/sales/

    • ビジネスドメイン(注文、顧客、商品など)で分類
    • 責務の明確化
  3. メタデータファイルを集約_tpch__models.yml, _orders__models.yml

    • 各フォルダに1つのYAMLファイルを配置
    • description、tests、metaをまとめて管理
  4. ディメンションテーブルを追加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失敗時のログを確認したか**

活用方法

  1. PRレビュー時にこのチェックリストを参照
  2. チーム内でレビュー観点を統一
  3. 見落としが減る

スキーマ戦略(開発/CI/本番環境)

dbtプロジェクトをチームで運用する際、環境ごとにスキーマを分離することが重要です。

図4: スキーマ戦略の全体図
図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.ymlschema(= target.schema)と、モデル側で指定した schema(= custom_schema_name)が異なる場合、
デフォルトでは {default_schema}_{model_schema} の形式でスキーマ名を結合します。

  • target.schema = PUBLIC
  • custom_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_RAWdbtのデフォルト動作target.schemacustom_schema_name の結合)
  • レイヤー別スキーマ(RAW/CORE/MART)を"そのまま"使いたいなら、generate_schema_name を上書きする
  • ただし チーム開発では衝突回避もセットで設計する(環境分離・スキーマ命名規約・権限設計など)

参考

スキーマ戦略のメリット

  • 開発環境の隔離:各開発者が独自のスキーマで作業でき、他のメンバーに影響しない
  • CI環境の並列実行:PR番号ごとにスキーマが分かれるため、複数PRが同時にCIを実行しても競合しない
  • 本番環境の保護:開発者が誤って本番スキーマを直接変更できないようにする

Part 4: 運用保守の勝ちパターン

リファクタリング戦略(モデル名変更・フォルダ再編成)

dbtプロジェクトを長期運用すると、「初期の命名が曖昧だった」「フォルダ構成を見直したい」といった理由でリファクタリングが必要になります。

リファクタリングの典型パターン

パターン 影響範囲
モデル名変更 orders_stagingstg_orders 下流モデルの ref() を全修正
フォルダ移動 models/stg_orders.sqlmodels/staging/tpch/stg_tpch__orders.sql ref() は変わらないが、パスが変わる
カラムリネーム customer_idcustomer_key 下流モデルのSELECTを全修正

リファクタリングの手順(モデル名変更の例)

シナリオorders_stagingstg_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: 破壊的変更の段階的移行フロー
図5: 破壊的変更の段階的移行フロー(3状態)。「旧カラムのみ(現状)」→「旧+新併存(移行期間)」→「新カラムのみ(完了)」の順に進め、下流(モデル/BI/アプリ)を段階的に切り替えます。矢印の「2週間」は例です。

段階的移行は「3つの状態」で考える

シナリオcustomer_idcustomer_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週間)
  1. 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') }}
  1. properties(schema.yml)に「非推奨」を明記します。
    meta は自由形式のメタ情報で、dbtが自動で「deprecated」を解釈して何かをしてくれるわけではありません(docs/lineageや運用ツール向けの情報として使います)。

また、dbt v1.10以降は meta / tagsconfig: 配下に寄せる形式が推奨されています(後方互換で 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から移行。今後はこちらを使用してください。
  1. 互換期間中に、下流モデルを順次 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_ordersstaging_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: 運用設計の3つの柱(再掲)
図1(再掲): 命名規約・フォルダ構成(一貫性)、チーム開発の運用ルール(協働)、運用保守の戦略(持続性)が相互に支え合い、スケールするdbtプロジェクトを実現する。

  1. 命名規約・フォルダ構成(一貫性)

    • stg_/int_/fct_/dim_のプレフィックス
    • ドメイン駆動設計的なフォルダ構成
    • メタデータ管理(schema.yml/sources.yml)
  2. チーム開発の運用ルール(協働)

    • ブランチ戦略・PR規約
    • レビューチェックリスト
    • スキーマ戦略(開発/CI/本番の分離)
  3. 運用保守の戦略(持続性)

    • リファクタリング戦略
    • 破壊的変更の段階的移行
    • パフォーマンス・コスト管理

今日から適用できる運用ルール

  1. 命名規約をREADMEに明記
  2. PR Descriptionテンプレートを設置
  3. レビューチェックリストを共有
  4. スキーマ戦略を統一(DBT_DEV_<ユーザー名>、DBT_PR_<PR番号>、DBT_PROD)
  5. 破壊的変更は段階的移行(新旧併存→移行→削除)

次のステップ

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公式ドキュメント

Snowflake公式ドキュメント

過去の記事

Discussion