【日本語解説】dbtベストプラクティス:成功するプロジェクト構造

こんにちは、六本木アナリティクスエンジニアのTaku(@aelabdata )です。

dbtでデータモデリングの設計を実装し始めると、「このテーブルにどんな名前を付ければいい?」「各レイヤーの下位ディレクトリはスキーマ単位がいい?」といった、より細かいルールの必要性を痛感しました。チームで開発を進める上では、こうした暗黙知を形式知化し、プロジェクト全体で統一された規律を持たせることが不可欠です。

そこで今回は、dbtプロジェクトの構造における「正解」を知るため、dbtの公式ドキュメント「How we structure our dbt projects」を熟読し、その内容を日本語でまとめました。

このベストプラクティスを学ぶことで、皆さんのdbtプロジェクトをより堅牢で、保守性が高く、チームで開発しやすいものに変えるヒントを得られるはずです。

公式ドキュメントの概要

公式ドキュメントの目的

このガイドは、dbtプロジェクトの構造に関するもので、チームが分析プロジェクトでより効果的に協力できるようにすることを目的としています。中心となる原則は、ファイルやフォルダの構成、命名規則、プログラミングパターンに一貫性のあるシステムを確立することです。これにより、チームメンバーは組織的な意思決定に時間を取られることなく、複雑な問題の解決に集中できるようになります。

  1. 最新の推奨事項の提供: 一般的なdbtプロジェクトの構造に関する最新の推奨事項を提供すること。
  2. 包括的な事例の提示: 仮想企業「Jaffle Shop」を例に挙げ、推奨事項を包括的に説明すること。
  3. 推奨の根拠の説明: 推奨されるアプローチの背後にある理由を説明し、ユーザーが状況に応じて最適な判断を下せるようにすること。

データ変換の3つの主要レイヤー

このガイドでは、modelsディレクトリ内のデータ変換プロセスを特定の順序で進めることを推奨しています。

  1. Staging(ステージング): ソースデータから初期のモジュール化された構成要素を作成します。データのクレンジングや基本的な整形を行います。
  2. Intermediate(中間): Stagingで作成したモデルにさらにロジックを追加し、複数のStagingモデルを結合するなどして、次のステップに向けた準備をします。
  3. Marts(マート): Intermediateで作成したモデルを統合し、ビジネス要件に沿ったエンティティ(例:顧客、注文、セッションなど)を作成します。

これらのレイヤーに加えて、YAML設定、テスト、シードデータ、分析ファイルなどの全体的なプロジェクト構造についても解説しています。最終的には、すべてのファイル構成を一覧で示し、構築される構造の全体像を明確にしています。

jaffle_shop
├── README.md
├── analyses
├── seeds
│   └── employees.csv
├── dbt_project.yml
├── macros
│   └── cents_to_dollars.sql
├── models
│   ├── intermediate
│   │   └── finance
│   │       ├── _int_finance__models.yml
│   │       └── int_payments_pivoted_to_orders.sql
│   ├── marts
│   │   ├── finance
│   │   │   ├── _finance__models.yml
│   │   │   ├── orders.sql
│   │   │   └── payments.sql
│   │   └── marketing
│   │       ├── _marketing__models.yml
│   │       └── customers.sql
│   ├── staging
│   │   ├── jaffle_shop
│   │   │   ├── _jaffle_shop__docs.md
│   │   │   ├── _jaffle_shop__models.yml
│   │   │   ├── _jaffle_shop__sources.yml
│   │   │   ├── base
│   │   │   │   ├── base_jaffle_shop__customers.sql
│   │   │   │   └── base_jaffle_shop__deleted_customers.sql
│   │   │   ├── stg_jaffle_shop__customers.sql
│   │   │   └── stg_jaffle_shop__orders.sql
│   │   └── stripe
│   │       ├── _stripe__models.yml
│   │       ├── _stripe__sources.yml
│   │       └── stg_stripe__payments.sql
│   └── utilities
│       └── all_dates.sql
├── packages.yml
├── snapshots
└── tests
    └── assert_positive_value_for_total_amount.sql
my_dbt_project/
├── models/
│   ├── staging/
│   ├── intermediate/
│   └── marts/
├── macros/
├── seeds/
├── tests/
├── analyses/
└── dbt_project.yml

Staging(ステージングレイヤー)

dbtプロジェクトにおいて、ステージングレイヤーは、ソースデータからより複雑なモデルを構築するための基礎となる、個々の「原子的な(atomic)」構成要素を作り出す場所です。このプロセスは、原子が分子、タンパク質へと形を変えていくメタファーで説明されています。

  • フォルダ構成:
    • modelsディレクトリ内には、データローダーやビジネスの分類ではなく、ソースシステム(例:jaffle_shopstripe)に基づいたサブディレクトリを作成することが推奨されます。これにより、同じソースシステムからのデータは類似したプロパティを持つことが多いため、**単一の真実の情報源(Single Source of Truth)**を確立しやすくなります。
  • ファイル命名規則:
    • 一貫した命名パターンが重要です。推奨される形式は stg_[source]__[entity]s.sql です。
    • ソースとエンティティを__(ダブルアンダースコア)で区切ることで、視覚的な明確さを確保します。
    • 可読性のために、エンティティ名は複数形(例:customers)を使用することが推奨されます。
  • ステージングモデルの構築:
    • ステージングモデルは通常、2つのCommon Table Expressions (CTEs) を使用します。1つ目のCTEでsourceマクロを使ってソーステーブルを呼び出し、2つ目のCTEで基本的な変換を適用します。
    • このレイヤーで行う基本的な変換は、リネーム型キャスト基本的な計算(例:セントをドルに変換)、条件ロジックによる分類などです。
  • マテリアライズ:
    • ステージングモデルはビューとしてマテリアライズすることが推奨されます。
    • これは、ステージングモデルが最終的な成果物ではなく、後のモデルを構築するための構成要素に過ぎないためです。ビューにすることで、ダウンストリームのモデルは常に最新のデータを取得でき、直接クエリされないモデルがウェアハウスのストレージを無駄に消費するのを防ぎます。
  • DRY原則の適用:
    • 個別の削除テーブルの結合や、対称的ながらも異なるソースの統合が必要な場合など、しっかりとした概念を構築するために結合が必要なケースでは、ステージングディレクトリ内にbaseサブディレクトリを作成して、最終的なステージングモデルの前にこれらの変換を処理することができます。
  • ベースモデル:
    • 個別の削除テーブルの結合や、対称的ながらも異なるソースの統合が必要な場合など、しっかりとした概念を構築するために結合が必要なケースでは、ステージングディレクトリ内にbaseサブディレクトリを作成して、最終的なステージングモデルの前にこれらの変換を処理することができます。
  • 自動化:
    • 手動でステージングモデルを書くことに慣れたら、codegenパッケージを使用して、ソースのYAMLファイルやステージングモデルのひな形を自動生成することで、開発プロセスを効率化することができます。

Intermediate(中間レイヤー)

中間レイヤーは、ステージングレイヤーで作成された「原子的な」データを、より複雑で接続された「分子的な」形に組み立てる場所です。これは、より複雑なデータプロダクト(データマートなど)を構築するためのステップとして機能します。

  • フォルダ構成:
    • ステージングレイヤーとは異なり、中間レイヤーのモデルはビジネスの分類(例:customersorders)に基づいたサブディレクトリに整理されます。これにより、ソースシステムから離れ、ビジネスに準拠した概念へと移行します。
    • このアプローチは、ビジネス要件に沿ったデータモデルを構築するのに役立ちます。
  • ファイル命名規則:
    • ファイル名は int_[entity]s_[verb]s.sql の形式を使用します。これにより、モデル内でどのような変換が行われているかが明確になります。
    • ソースシステムレベルで操作する場合は、ファイル名は int_[source]__[entity]s_[verb]s.sqlの形式にします。
    • [verb] には、pivoted(ピボットされた)、aggregated_to_user(ユーザーごとに集計された)、joined(結合された)などの動詞が含まれ、アクションを記述します。
  • 中間モデルの主なユースケース:
    • 構造の単純化: 複数のステージングモデルを結合して、より単純なビューを作成します。
    • 粒度の変更(re-graining): データの粒度をビジネス要件に合わせて変更します(例:注文ごとのデータを顧客ごとに集計する)。
    • 複雑な操作の分離: 複雑なロジックや計算を分離し、再利用可能なコンポーネントにします。
  • マテリアライズ:
    • 中間モデルはエンドユーザーに直接公開されることを想定していないため、ephemeral(一時的)またはviewとしてマテリアライズすることが推奨されます。
    • これにより、データウェアハウスを整理された状態に保ちつつ、ダウンストリームのモデルで常に最新のデータを利用できます。
  • DAG(有向非巡回グラフ)の形状:
    • このレイヤーでは、モデルのDAGが「矢じり」のような形状になることを理想としています。つまり、複数の入力(ステージングモデル)から1つの出力(中間モデル)を作成することで、複雑なプロセスをシンプルにまとめています。

Marts(マートレイヤー)

マートレイヤーは、コアなデータ変換プロセスの最終段階であり、ステージングモデルや中間モデルからのデータを統合して、エンドユーザーの利用を目的としたビジネス定義のエンティティを作成する場所です。これは、データアナリストや意思決定者が直接利用する層となります。

  • フォルダ構成:
    • マートは、部署や関心領域(例:marketingfinanceproduct)に基づいてフォルダにグループ化されるべきです。これにより、ビジネスの文脈に沿ったデータの整理が可能になります。
  • ファイル命名規則:
    • ファイル名は、マートの粒度(grain)となる概念に基づき、平易な英語で名付けることが推奨されます(例:customersordersdaily_sessions)。
    • 複数のチームのために同じ概念を異なる方法で構築することはアンチパターンと見なされます。一つの概念に対して、一つのマートを作成することが重要です。
  • マテリアライズ:
    • マートモデルは、エンドユーザーのクエリパフォーマンスを向上させ、データ再計算のコストを削減するために、通常テーブルまたはインクリメンタルモデルとしてマテリアライズされます。
    • ビューとしてマテリアライズされた場合、利用者は常に最新のデータを取得できますが、クエリのたびにデータが再計算されるため、パフォーマンスの低下を招く可能性があります。
  • データ構造とロジック:
    • dbtのSemantic Layerを使用しないプロジェクトでは、マートは非常に非正規化された(denormalized)構造にすることが推奨されます。これにより、エンドユーザーはBIツールなどでデータを扱いやすくなります。
    • 一つのマートに多数のJOINを含めることは避け、複雑なロジックは中間モデルにモジュール化することが推奨されます。
    • マートは、特定のエンティティに関する全ての有用なデータを、できるだけ詳細な粒度で含み、ワイド(広範なカラムを持つ)で非正規化された構造にすることが、重要なポイントです。

モデル以外のプロジェクト構成

このガイドは、modelsフォルダだけでなく、dbtプロジェクト全体の構造に関するベストプラクティスを解説しています。一貫性のある構造を確立することが、プロジェクトを管理しやすくするために不可欠だと強調されています。

YAML設定ファイルの構成

YAML設定ファイル(dbt_project.ymlやスキーマファイル)の整理方法について、以下の推奨事項が示されています。

  • 「フォルダごとの設定」アプローチ:
    • config per folderというアプローチが推奨されます。これは、modelsフォルダ内の各ディレクトリ(例:stagingintermediatemarts)ごとにYAML設定ファイルを作成する方法です。
    • 例として、_[ディレクトリ]__models.yml や _[ディレクトリ]__sources.yml といった命名規則が挙げられています。
    • これにより、設定ファイルが過剰に分散したり、逆に巨大になりすぎたりするのを防ぎ、特定の構成にアクセスしやすくなります。
  • dbt_project.ymlの活用:
    • dbt_project.ymlを使って、ディレクトリレベルでデフォルトの設定(例:マテリアライズ方法)を定義することで、繰り返し設定を書く手間を省くことができます。

その他の主要なフォルダとファイル

modelsフォルダ以外にも、dbtプロジェクトにはいくつかの重要なフォルダがあります。

  • seeds:
    • ルックアップテーブルや小規模なデータセットなど、CSVファイルとしてプロジェクトに含めるデータを配置するフォルダです。
  • analyses:
    • 監査用のクエリや、特定のビジネスインサイトを探索するためのアドホックなクエリを保存する場所です。
  • tests:
    • 複数の特定のテーブルに対して同時にテストを実行するためのカスタムテストを配置する場所です。
  • snapshots:
    • レコードの変更履歴を追跡するためのもので、Type 2 slowly changing dimensions(SCD Type 2)を実装するために使用します。
  • macros:
    • 再利用可能なSQLコードやロジック(例:共通の計算式)を定義し、プロジェクト全体で利用するためのフォルダです。

複数のdbtプロジェクトに分割するタイミング

ガイドは、コードベースを複数のdbtプロジェクトに分割する際の判断基準についても言及しています。

  • 推奨される分割のケース:
    • ビジネスグループが異なる場合(例:マーケティングチームと財務チーム)。
    • データガバナンス上の理由がある場合。
    • プロジェクトの規模が非常に大きくなった場合。
  • 非推奨の分割のケース:
    • 機械学習とレポート作成といった、単にユースケースが異なるだけで分割することは推奨されません。

全体のメッセージとして、これらのベストプラクティスはあくまでガイドラインであり、一貫性を保ち、慣習から逸脱した場合はその理由を文書化することが重要であると締めくくられています。

おわりに

dbtのプロジェクト構造を適切に設計することは、チームで持続可能なデータ基盤を構築するための基盤です。今回ご紹介したベストプラクティスは、以下の効果をもたらします。

  • 可読性と保守性の向上: 誰が見てもデータの流れと役割が理解しやすくなります。
  • 開発効率の向上: 再利用可能なコンポーネントが増え、新しいモデル開発が迅速になります。
  • データ品質の担保: テストの実装が容易になり、信頼できるデータを提供できます。

この記事が役に立ったと感じたら、ぜひX(@aelabdata)をフォローください!日々のアナリティクスエンジニアとしての学びや、記事の更新情報を発信しています。