- 背景: 組織独自のレイヤリング構造の制約をCI上で表現したい
- 課題: CI時にレイヤリングの情報をどう取得するか
- 解決方法: dbt-project-evaluatorが内部で構築するテーブルを利用する
- 補足: その他の設定
背景: 組織独自のレイヤリング構造の制約をCI上で表現したい
- dbtを使ってデータのtransformを行なう場合、何かしらのレイヤリングを設計することが多いと思います
- 例: データレイク => ステージング => dwh => マート
- 例: staging => raw vault => business vault => fact & dim
- レイヤリングを設計するということは設計したレイヤリングに沿わないデータリネージは基本的には許さない、ということを意味します
- 例: マートからデータレイクを直接参照しない
- 単純なリネージだけでなく、組織独自のリネージ上の制約を入れたい場合もあると思います
- 例: adhocからadhocを参照させたくない
- 例: business vaultからbusiness vaultを参照するのは許可するが、参照が多段になりbusiness vault内で例えば5段の参照関係がある、などは許可したくない
- レビューの際に人間がこういった制約を毎回考慮するのは骨が折れます
- 特に「チーム開発をしている場合」や「アナリティクスエンジニアリングには慣れているが、その組織での独自のレイヤリング構造のルールに慣れていない場合」などはこの制約を守り続けるのは大変です
- SQLファイルをparseしてルールを守っているかを判断するスクリプトを書くこともできますが、運用がちょっと面倒ではあります
dbt test
内で一緒に担保して欲しい
- dbtが推奨するプロジェクト構成などの規約をテストするdbt-project-evaluatorというパッケージが存在しますが、自社のルールがこれにぴったり合わせるのが難しい場合もあると思います
課題: CI時にレイヤリングの情報をどう取得するか
- elementaryを使うことで、dbtのモデルに対するメタデータやモデル間の情報は取得できるが、elementaryはon-run-endのタイミングで動く
- そのため、Pull Requestを出してもらった際のCIが動くタイミングだと、新規で作られたdbtのモデルの情報がelementaryにまだ入っていないため、elementaryはこの課題には不向き
解決方法: dbt-project-evaluatorが内部で構築するテーブルを利用する
- dbt-project-evaluatorが内部で構築するテーブルを利用すると、組織独自のレイヤリングの制約をSQLで簡単に書くことができます
- dbt-project-evaluatorが内部で構築するテーブルの例1:
int_all_dag_relationships
- これはdbtのモデルの親子関係などdagの情報を集約したテーブルです
- 直接親子関係がある場合だけでなく、A => B => Cというリネージがあった際のAとCのような間接的に影響があるような情報も入っています
A => B => C
のような情報はpath
、2つ離れた関係であるという情報はdistance
という情報で入っています- また、参照元、参照先のモデルのidやファイル名、ディレクトリ名といった情報を取ることができます
- 詳細は以下のスキーマを見てもらうと雰囲気が分かるかと思います
- ref: Querying the DAG - dbt_project_evaluator
- dbt-project-evaluatorが内部で構築するテーブルの例2:
stg_nodes
int_all_dag_relationships
がモデル間の情報を保持していたのに対し、stg_nodes
はモデルのよりチッチな情報を保持します- ファイルの行数やyamlに埋め込まれている
meta
の情報なども含まれます
これらのテーブルを使うと、組織独自の制約をSQLで簡単に表現することができます。例えば、「adhocからadhocを参照させたくない」という制約の場合は以下のように書けます。
select parent, child, distance, from {{ ref("int_all_dag_relationships") }} where parent_resource_type = "model" and parent_directory_path like "path/to/adhoc%" and child_directory_path like "path/to/adhoc%"
また、「同一のディレクトリ(レイヤ)内で多重の参照関係を許したくない」という場合の制約は以下のように書けます。
with ignore_depth_limit_nodes as ( select unique_id from {{ ref("stg_nodes") }} where cast(json_extract_scalar(meta, '$.dbt_project_evaluator_ignore_depth_limit') as bool) ) select parent, child, distance, from {{ ref("int_all_dag_relationships") }} where parent_resource_type = "model" and parent_directory_path = child_directory_path and distance >= 3 -- 3はただの例です and child_id not in (select unique_id from ignore_depth_limit_nodes)
ここで、dbt_project_evaluator_ignore_depth_limit
という謎の名前が出てきますが、これはyaml内で独自に設定する値になります。「新規で作るモデルはこの制約を満たしているものしか受け入れないようにしたい(傷口を広げたくない)が、既存のモデルはリファクタリングをしないと制約を満たせない。しかし、運用自体はしなければならない」という場合にdbt_project_evaluator_ignore_depth_limit
が設定されているモデルはスキップさせる、というような柔軟な運用をすることができます。
version: 2 models: - name: my_child_model meta: dbt_project_evaluator_ignore_depth_limit: true
このテストはSingular data testsとして記述しておけばdbt test
時に実行してくれるので、CI上のややこしい設定をせずSQLだけで制約を簡潔に済ますことができます。
補足: その他の設定
導入の際は以下も一緒に設定しました。
# BigQueryの場合、入れないと落ちてしまうので入れておく # https://github.com/dbt-labs/dbt-project-evaluator/issues/421 dispatch: - macro_namespace: dbt search_order: ['dbt_project_evaluator', 'dbt'] # dbt-project-evaluatorで元から入っているテストで不要なものは無効にしておく # https://dbt-labs.github.io/dbt-project-evaluator/latest/customization/customization/ models: dbt_project_evaluator: marts: dag: +enabled: false documentation: +enabled: false governance: +enabled: false performance: +enabled: false structure: +enabled: false tests: +enabled: false