dbtプロジェクト構築に関するベストプラクティス #5 「モデル以外のプロジェクト構成要素に関する検討事項」 #dbt

2024.03.25

アライアンス事業部 エンジニアグループ モダンデータスタック(MDS)チームのしんやです。

dbtはクラウド型データウェアハウス(DWH)におけるデータ変換に特化したツールです。非常に使い勝手が良く便利なツールである一方、様々な機能が提供されているのでいざ使ってみよう!となると『何をどうやって作り上げていけば良いんだろう?』『この場合のルールや制限はどういうものがあるの?どういう取り決めをもって扱えば良いんだろう?』という風に思うこともあるかと思います。(実際私自身そう感じました)

そんなユーザーの疑問や悩みを解決する、いわゆるdbtユーザー向けのガードレール的な存在となりうるコンテンツがdbt社から展開されています。それが『dbtベストプラクティスガイド(Best practice guides)』です。構造、スタイル、セットアップなど、dbt Labsの現在の視点を通した「ベストプラクティス」がまとめられています。

そこで当エントリでは、幾つか展開されている「dbtベストプラクティスガイド」で紹介されているコンテンツの中から『dbtプロジェクト構築』に関するものの中で、既存エントリで公開してきたもの『以外の要素』についてどのようにdbtで構築していくかについて読み解いていきたいと思います。

過去の『dbtプロジェクト構築』に関するベストプラクティス一覧:

目次

 

プロジェクト構成レビュー

過去4本のエントリでは、dbtプロジェクトの主要なディレクトリであるmodelsフォルダに焦点を当ててきました。当エントリではYAML設定ファイルへのアプローチ方法から始めて、プロジェクトの残りのファイルやフォルダがこの構造にどのように適合しているかを拡大して見ていきます。

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

 

YAMLの詳細

dbtプロジェクトに於いてYAML設定ファイルの構造化を検討する際、任意の設定ファイルをできるだけ素早く見つけられるようにするために、ファイルサイズと一元化の良いバランスを取りたいところです。トップレベルのYAMLファイルであるdbt_project.yml, packages.ymlはファイルパス(最上位に配置)とファイル名(この名前)を遵守する必要がありますが、ソースとモデルに関する設定ファイルは好きなファイル名、ファイルパス、ファイル構成にすることが出来ます。重要なのはファイルの中身です。

以下に推奨するルール、避けておくべきルールを列挙します。


✅ すべき(dos) ❌ べからず(dont’s)
フォルダごとに設定ファイルを設ける
・上記例のように、modelsフォルダのディレクトリごとに_[directory]__models.ymlを作成し、そのディレクトリ内のすべてのモデルを設定。
・ステージングフォルダの場合は、ディレクトリごとに_[directory]__sources.ymlも作成。  ・先頭にアンダースコアを付けることでYAMLファイルがすべてのフォルダーの先頭にソートされ、モデルから簡単に分離できるようになります。
 ・YAMLファイルには、SQLモデルにおけるファイル名のように一意の名前は必要ないが、(各フォルダー内の単に _sources.yml ではなく) ディレクトリを含めることにより、適切なファイルをより迅速に曖昧に見つけることができます。
 ・dbtでは長年にわたっていくつかの異なる命名規則を推奨してきたが、最近ではこれらのファイルはschema.ymlファイルと呼ばれている。
 ・プロジェクトでdocブロックを利用する場合は、同じパターンに従い、そのモデルフォルダーのすべてのdocブロックを含むディレクトリごとに_[ディレクトリ]__docs.mdというマークダウンファイルを作成することをお勧めします。

設定を「プロジェクト毎」に行う
「全てのSourceとModelのYAMLファイルを1つのファイルにまとめる」というのは技術的には可能ですが、そのファイル内で特定の構成を見つけるのが非常に困難になります。設定ファイルの分割をお勧めしますが、その分割の粒度についてもバランスを取る必要があります。
【注意】モデル毎の設定ファイル構成
・「プロジェクトで1つの設定ファイル」とは異なり、「モデル毎に1つのファイル」を好む人もいます。
・この形は「ファイルが素早く検索出来る」「特定の構成が存在する場所を正確に把握出来る」など様々なメリットがある一方でファイル操作やウインドウ操作の観点から開発エクスペリエンスが多少遅くなる傾向があります。
・「プロジェクト毎」と「モデル毎」の間を取った『ディレクトリごとに構成を定義』するのは、ほとんどのプロジェクトにとって最もバランスの取れたアプローチですが、モデルごとに構成を使用するやむを得ない理由がある場合は、この案を検討してみても良いのかもしれません。
----
カスケード構成
・dbt_project.yml を利用して、ディレクトリ レベルでデフォルト構成を設定します。 ・これまでに作成した、よく整理されたフォルダー構造を使用してベースラインのスキーマと具体化を定義し、dbt のカスケードスコープの優先順位を使用してこれに対するバリエーションを定義します。
・例えばデフォルトでテーブルとして実体化されるようにマートを定義し、個別のサブフォルダーに個別のスキーマを定義すると、増分実体化を使用する必要があるモデルをモデルレベルで定義できます。

dbt_project.yml

-- dbt_project.yml

models:
  jaffle_shop:
    staging:
      +materialized: view
    intermediate:
      +materialized: ephemeral
    marts:
      +materialized: table
      finance:
        +schema: finance
      marketing:
        +schema: marketing
----
デフォルトの設定を定義しておく
・プロジェクト構造に対する、当エントリで言及しているアプローチによってもたらされる多くの利点の1つは『デフォルトの動作をカスケード(連結・連鎖)できる』という点です。
・フォルダーを慎重に整理し、可能な限りそのレベルで構成を定義することで、すべての単一モデルでスキーマや具体化などを構成することから解放され。一般的なルールの例外を構成するだけで済むようになります。
・『要素に対するタグ付け』もこの原則が関係してきます。主要なセレクターおよびグループ化メカニズムとしてフォルダーを活用し、タグを使用して例外となるグループを定義することで柔軟な対応が可能となります。
dbt build --select marts.marketing:フォルダベースの指定例
dbt build --select tag:marketing:タグベースの指定例


参考:
tags | dbt Developer Hub
dbtのtagについて細かな挙動を確認してみた #dbt | DevelopersIO

----

その他フォルダの使用方法

上記で言及してきたものはdbtプロジェクトで開発を進めていく上で中心・肝となる部分にフォーカスした内容でしたが、dbtプロジェクトにはその他のフォルダやファイル構成もあります。

jaffle_shop
├── analyses
├── seeds
│   └── employees.csv
├── macros
│   ├── _macros.yml
│   └── cents_to_dollars.sql
├── snapshots
└── tests
└── assert_positive_value_for_total_amount.sql

シード(seeds)

✅ すべき(dos) ❌ べからず(dont’s)
[seeds]ルックアップ(参照用)テーブル用途としてのみ使う
・シード[seeds]の最も一般的な使用例は『モデリングには役立つもののソース システムには存在しないルックアップ テーブルを読み込むこと』。この目的に沿うような使い方をすべきです。例えば、郵便番号を州にマッピングしたり、UTM パラメータをマーケティング キャンペーンにマッピングしたりする...などです。
・上記例では、特別なロジックで従業員の購入を処理できるように、従業員をcustomer_idにマップするための小さなシードファイル(employees.csv)があります。
[seeds]ソースデータをロードするために使う
・ソースシステムからウェアハウスにデータをロードするためにシードを使用しないでください。
・アクセスできるシステムにファイルが存在する場合は、適切なELツールを使用してウェアハウスの生データ領域にロードべきです。
・dbtはそもそも、データ読み込みツールとしてではなく、ウェアハウス内のデータを操作するように設計されているサービスです。

アナライシズ(analyses)

✅ すべき(dos) ❌ べからず(dont’s)
[analyses]監査クエリの保存用として利用
・analysesフォルダーを使用すると、Jinjaとバージョン管理を併用するクエリを保存できます。ちなみにここに格納したクエリはウェアハウス内のモデルには組み込まれません。
・利用用途の夢は広がりますが、dbt Labs でプロジェクトをセットアップするときの最も一般的な使用例は、監査ヘルパーパッケージを活用するクエリをここに保持することです。
・このパッケージは、ロジックを別のシステムからdbtに移行するときに、出力の不一致を見つけるのに非常に役立ちます。
----

テスト(tests)

✅ すべき(dos) ❌ べからず(dont’s)
[test]複数の特定のテーブルを同時にテストする
・dbtテストが進化するにつれて、単一のテストを作成する必要性はますます低くなりました。これはテストロジックのワークショップに非常に便利ですが、多くの場合、そのロジックを独自のカスタム汎用テストに移行するか、拡大し続ける dbt パッケージの世界からニーズを満たす事前に構築されたテストを見つけることになります。 (dbt-utils と dbt-expectations の追加テストの間では、ほぼすべての状況がカバーされます)。
・単一のテストが依然として優れている領域の1つは、さまざまな特定のモデルを必要とするものを柔軟にテストすることです。ソフトウェアエンジニアリングにおける単体テストと統合テストの違いをよく理解している場合は、汎用テストと単一テストを同様に考えることができます。
・複数の特定のモデルがどのように相互作用するか、または相互に関連するかという結果をテストする必要がある場合は、単一のテストを行うことが、ロジックを正確に特定する最も簡単な方法となる可能性があります。
----

スナップショット(snapshots)

✅ すべき(dos) ❌ べからず(dont’s)
[snapshots]type1(破壊的に更新される)ソースデータからtype2(slowly changing dimension)のレコードを作成
・これについては、dbtドキュメントで詳しく説明されています。:Add snapshots to your DAG | dbt Developer Hub
----

マクロ(macros)

✅ すべき(dos) ❌ べからず(dont’s)
[macro]繰り返し行う変換をDRYにするために用いる
・スナップショットと同様、マクロの詳細については、このガイドの範囲外であり、別の場所で十分カバーされています。
 ・Jinja and macros | dbt Developer Hub
・構造に関する重要な推奨事項の1つは、マクロのドキュメントを書くことです。_macros.ymlを作成し、マクロが使えるようになったら、その目的と引数を文書化することをお勧めします。
 ・How do I document macros? | dbt Developer Hub
----

 

プロジェクトの分割

アナリティクス・エンジニアリングのエコシステムにおいて、重要な検討事項のひとつに、『コードベースを複数のdbtプロジェクトに分割する方法とタイミング』があります。

dbt Labs社の見解としては、ほとんどのプロジェクト、特にスタートアップのチームに対して、「他に選択肢がないか、より複雑な回避策を省くことができない限り、分割は避けるべきだ」というものです。

プロジェクトを分割する必要がある場合は、プライベートパッケージを使うことで完全に可能ですが、複雑さと分離が追加されることは、ほとんどの組織にとって、現在のところ、助けになるどころか邪魔になります。とはいえ、これは変更される可能性が高いです。dbt Labs社では、たくさんのdbtプロジェクトを簡単にひとつの系統にまとめられる世界を作りたい...と考えているようです。今後のdbt Labs社及びdbt関連プロダクトがどのように進化発展していくかが気になるところではありますが、将来を見守っていきましょう。

ということでプロジェクト分割における『すべき/べからず』集です。

✅ すべき(dos) ❌ べからず(dont’s)
データガバナンス(の観点で分割)
・データガバナンスやセキュリティなどの構造的、組織的なニーズは、プロジェクトを分割する価値のある数少ない理由の1つです。
・たとえば、PII を含む生データへのアクセスを許可された少数のチームしかいない医療会社で働いている場合、それらのポリシーを維持するために、ステージングモデルを独自のプロジェクトに分割する必要があるかもしれません。
・その場合、ステージングプロジェクトを、それらのステージングモデル上に構築されるプロジェクトにプライベートパッケージとしてインポートします。
 ・Private Packages - Packages | dbt Developer Hub
ビジネスグループまたは部門で分ける
・プロジェクト内の概念的な分離は、プロジェクトを分割する十分な理由にはなりません。
・例えば、マーケティングと財務のモデリングを別々のプロジェクトに分割すると、不必要な複雑さが増すだけでなく、一貫した定義とビジネスロジックに関して組織全体で協力することによる統一効果が損なわれます。
プロジェクトのサイズ(で分割)
・ある時点で、プロジェクトが成長し、実行可能な開発エクスペリエンスを提供するにはモデルが多すぎる場合があります。
・何千ものモデルがある場合、プロジェクトを分割する方法を見つけることは非常に合理的です。
MLとレポートの使用例
・様々なユースケース、特に標準的なBI機能とML機能に基づいてプロジェクトを分割することが一般的なアイデアです。私たちは当分の間それをやめてしまう傾向があります。
・dbt を実装する基本的な目標は、組織内に単一の信頼できる情報源を作成することです。 データ サイエンス チームに提供する機能は、エグゼクティブ ダッシュボードでレポートを提供するものと同じマートとメトリクスから得られるものである必要があります。
・falやContinual.aiなど、この統一された視点をうまく活用するツールが増えています。
 ・Continual | The AI Copilot Platform for Applications

 

まとめ

という訳で、『dbtプロジェクト構築』に関するベストプラクティス最終回(あ、Semantic Layer向けのマート層のやつがあったけどまぁ良いか)、モデルにおける各種レイヤー層以外の要素に関する構成や配置等に関するベストプラクティスを見ていきました。

今回の内容に限らず、『ベストプラクティス』はあくまで参考情報です。dbt Labs社が推奨するガイドラインを学び、自分達が関わるプロジェクトや組織のこれまでのルールや慣習を踏まえて『良き落とし所』となるルールやガイドライン等を作成し、文書化してプロジェクトに適用していくのが何より重要です。そしてdbtやdbt Labsが進化していくのと合わせてプロジェクトの内容も変化、成長していけるように適宜ルールを見直し、改善していきたいですね。