dbt_project_evaluator を触ってみたので、その時のメモをここに残す。
dbt_project_evaluator とは
自分の dbt プロジェクトが dbt Labs 公式のベストプラクティスに従っているかを評価してくれる dbt パッケージ。
次の6タイプのルールが用意されている。
| Type | ルール例 |
|---|---|
| Modeling |
|
| Testing |
|
| Documentation |
|
| Structure |
|
| Performance |
|
| Governance | model governance についてのルールが用意されている |
導入方法
dbt_project_evaluator の Package Hub ページの Installation のとおり。
packages.yml に以下を追加。
packages:
- package: dbt-labs/dbt_project_evaluator
version: 1.2.4
dbt deps を実行してインストール。
なお、筆者は下記の環境で動かした。
[project]
requires-python = ">=3.13"
dependencies = [
"dbt-bigquery>=1.11.1",
"dbt-core>=1.11.8",
]
ルールの設定
デフォルト設定だと全てのルールが有効になっている。実運用では、プロジェクトの状況に応じて不要なルールを無効にすることが多いと思われる。
以下に、 Hard Coded References ( ref() を使わずに直接テーブル名を指定していないかを評価するルール)を有効にして、その他のルールを無効にする例を示す。
dbt_project.yml
models:
dbt_project_evaluator:
marts:
dag:
+enabled: false
fct_hard_coded_references:
+enabled: true
documentation:
+enabled: false
governance:
+enabled: false
performance:
+enabled: false
structure:
+enabled: false
tests:
+enabled: false
挙動
dbt_project_evaluator は次のコマンドを実行することで動かせる。
dbt build --select package:dbt_project_evaluator
挙動は次のとおり:
- ルールを評価するための dbt モデルを生成
- 生成された dbt モデルに対して dbt テストを実行することで、ルールに沿っているかを評価
例えば、次のような dbt モデルが定義された dbt プロジェクトを考える。
{{ config(materialized='table') }}
with source_data as (
select 1 as id
union all
select null as id
)
select *
from source_data
select *
from
-- {{ ref('my_first_dbt_model') }}
-- わざと Hard Coded References ルールに引っ掛かるようにしてみる
`YOUR_GC_PROJECT.YOUR_DATASET.my_first_dbt_model`
where id = 1
この dbt プロジェクトに Hard Coded References ルールだけ有効化した dbt_project_evaluator を実行すると、実行ログは次のようになる。
実行ログ
❯ dbt build --select package:dbt_project_evaluator
08:13:07 Running with dbt=1.11.8
08:13:07 Registered adapter: bigquery=1.11.1
08:13:08 Found 21 models, 1 seed, 5 data tests, 722 macros
08:13:08
08:13:08 Concurrency: 1 threads (target='dev')
08:13:08
08:13:09 1 of 21 START sql table model YOUR_DATASET.base_exposure_relationships .. [RUN]
08:13:12 1 of 21 OK created sql table model YOUR_DATASET.base_exposure_relationships [CREATE TABLE (0.0 rows, 0 processed) in 2.39s]
08:13:12 2 of 21 START sql table model YOUR_DATASET.base_metric_relationships .... [RUN]
08:13:14 2 of 21 OK created sql table model YOUR_DATASET.base_metric_relationships [CREATE TABLE (0.0 rows, 0 processed) in 2.60s]
08:13:14 3 of 21 START sql table model YOUR_DATASET.base_node_columns ............ [RUN]
08:13:18 3 of 21 OK created sql table model YOUR_DATASET.base_node_columns ....... [CREATE TABLE (0.0 rows, 0 processed) in 3.79s]
08:13:18 4 of 21 START sql table model YOUR_DATASET.base_node_relationships ...... [RUN]
08:13:22 4 of 21 OK created sql table model YOUR_DATASET.base_node_relationships . [CREATE TABLE (0.0 rows, 0 processed) in 3.74s]
08:13:22 5 of 21 START sql table model YOUR_DATASET.base_source_columns .......... [RUN]
08:13:24 5 of 21 OK created sql table model YOUR_DATASET.base_source_columns ..... [CREATE TABLE (0.0 rows, 0 processed) in 2.24s]
08:13:24 6 of 21 START sql table model YOUR_DATASET.stg_exposures ................ [RUN]
08:13:26 6 of 21 OK created sql table model YOUR_DATASET.stg_exposures ........... [CREATE TABLE (0.0 rows, 0 processed) in 2.25s]
08:13:26 7 of 21 START sql table model YOUR_DATASET.stg_metrics .................. [RUN]
08:13:28 7 of 21 OK created sql table model YOUR_DATASET.stg_metrics ............. [CREATE TABLE (0.0 rows, 0 processed) in 2.03s]
08:13:28 8 of 21 START sql view model YOUR_DATASET.stg_naming_convention_folders . [RUN]
08:13:29 8 of 21 OK created sql view model YOUR_DATASET.stg_naming_convention_folders [CREATE VIEW (0 processed) in 0.58s]
08:13:29 9 of 21 START sql view model YOUR_DATASET.stg_naming_convention_prefixes [RUN]
08:13:30 9 of 21 OK created sql view model YOUR_DATASET.stg_naming_convention_prefixes [CREATE VIEW (0 processed) in 0.76s]
08:13:30 10 of 21 START sql table model YOUR_DATASET.stg_nodes ................... [RUN]
08:13:34 10 of 21 OK created sql table model YOUR_DATASET.stg_nodes .............. [CREATE TABLE (0.0 rows, 0 processed) in 4.75s]
08:13:34 11 of 21 START sql table model YOUR_DATASET.stg_sources ................. [RUN]
08:13:37 11 of 21 OK created sql table model YOUR_DATASET.stg_sources ............ [CREATE TABLE (0.0 rows, 0 processed) in 2.42s]
08:13:37 12 of 21 START seed file YOUR_DATASET.dbt_project_evaluator_exceptions .. [RUN]
08:13:40 12 of 21 OK loaded seed file YOUR_DATASET.dbt_project_evaluator_exceptions [INSERT 0 in 2.79s]
08:13:40 13 of 21 START sql view model YOUR_DATASET.stg_exposure_relationships ... [RUN]
08:13:40 13 of 21 OK created sql view model YOUR_DATASET.stg_exposure_relationships [CREATE VIEW (0 processed) in 0.56s]
08:13:40 14 of 21 START sql view model YOUR_DATASET.stg_metric_relationships ..... [RUN]
08:13:41 14 of 21 OK created sql view model YOUR_DATASET.stg_metric_relationships [CREATE VIEW (0 processed) in 1.05s]
08:13:41 15 of 21 START sql table model YOUR_DATASET.stg_node_relationships ...... [RUN]
08:13:43 15 of 21 OK created sql table model YOUR_DATASET.stg_node_relationships . [CREATE TABLE (38.0 rows, 3.4 KiB processed) in 2.21s]
08:13:43 16 of 21 START sql view model YOUR_DATASET.stg_columns .................. [RUN]
08:13:44 16 of 21 OK created sql view model YOUR_DATASET.stg_columns ............. [CREATE VIEW (0 processed) in 0.86s]
08:13:44 17 of 21 START sql table model YOUR_DATASET.int_all_graph_resources ..... [RUN]
08:13:48 17 of 21 OK created sql table model YOUR_DATASET.int_all_graph_resources [CREATE TABLE (6.0 rows, 10.3 KiB processed) in 3.68s]
08:13:48 18 of 21 START sql table model YOUR_DATASET.fct_hard_coded_references ... [RUN]
08:13:50 18 of 21 OK created sql table model YOUR_DATASET.fct_hard_coded_references [CREATE TABLE (1.0 rows, 283.0 Bytes processed) in 2.42s]
08:13:50 19 of 21 START sql table model YOUR_DATASET.int_direct_relationships .... [RUN]
08:13:53 19 of 21 OK created sql table model YOUR_DATASET.int_direct_relationships [CREATE TABLE (6.0 rows, 4.4 KiB processed) in 3.02s]
08:13:53 20 of 21 START test is_empty_fct_hard_coded_references_ ........................ [RUN]
08:13:54 20 of 21 WARN 1 is_empty_fct_hard_coded_references_ ............................ [WARN 1 in 0.91s]
08:13:54 21 of 21 START sql table model YOUR_DATASET.int_all_dag_relationships ... [RUN]
08:13:58 21 of 21 OK created sql table model YOUR_DATASET.int_all_dag_relationships [CREATE TABLE (3.0 rows, 2.0 KiB processed) in 3.33s]
08:13:58
08:13:58 Finished running 1 seed, 14 table models, 1 test, 5 view models in 0 hours 0 minutes and 49.82 seconds (49.82s).
08:13:58
08:13:58 Completed with 1 warning:
08:13:58
08:13:58 Warning in test is_empty_fct_hard_coded_references_ (models/marts/dag/dag.yml)
08:13:58 Got 1 result, configured to warn if != 0
08:13:58
08:13:58 compiled code at target/compiled/dbt_project_evaluator/models/marts/dag/dag.yml/is_empty_fct_hard_coded_references_.sql
08:13:58
08:13:58 Done. PASS=20 WARN=1 ERROR=0 SKIP=0 NO-OP=0 TOTAL=21
ログの最後の方を見てみると、 is_empty_fct_hard_coded_references_ という dbt テストで結果が 0 件でなかったために warning が出ていることが確認できる。
08:13:58 Completed with 1 warning:
08:13:58
08:13:58 Warning in test is_empty_fct_hard_coded_references_ (models/marts/dag/dag.yml)
08:13:58 Got 1 result, configured to warn if != 0
08:13:58
08:13:58 compiled code at target/compiled/dbt_project_evaluator/models/marts/dag/dag.yml/is_empty_fct_hard_coded_references_.sql
compiled code である is_empty_fct_hard_coded_references_.sql を見てみると、次のような SQL が記載されている。
select count(*) as n_records
from `YOUR_GC_PROJECT`.`YOUR_DATASET`.`fct_hard_coded_references`
fct_hard_coded_references テーブルの中身を確認してみると、次の1件のレコードが入っていた。
model hard_coded_references
----- ---------------------
my_second_dbt_model `YOUR_GC_PROJECT.YOUR_DATASET.my_first_dbt_model`
このレコードから my_second_dbt_model で YOUR_GC_PROJECT.YOUR_DATASET.my_first_dbt_model を ref() を使わずに直接参照していることがわかる。
GitHub Actions での実行
dbt_project_evaluator は dbt テストがこけた時の重要度をデフォルトだと「 warning 」に設定している。GitHub Actions 等で実行する場合、 「 error 」にしないと運用しづらい。
その場合、次のように設定することで重要度を変更できる。
data_tests:
dbt_project_evaluator:
+severity: "{{ env_var('DBT_PROJECT_EVALUATOR_SEVERITY', 'error') }}"
これにより、次のような感じで GitHub Actions ワークフローのステップを組んであげれば、 dbt_project_evaluator で引っかかった時に CI を落とすことができる。
jobs:
YOUR_JOB_NAME:
runs-on: ubuntu-latest
steps:
- 〜〜(中略)〜〜
- name: Build dbt_project_evaluator models
id: evaluator_tests
run: dbt build --select package:dbt_project_evaluator
- name: Fail if evaluator tests failed
if: steps.evaluator_tests.outcome == 'failure'
run: exit 1
運用時の留意点
普通に dbt run すると models/ 配下の dbt モデルに加えて dbt_project_evaluator の dbt モデルも生成される。
多くの dbt プロジェクトでは本番環境と開発環境を分けて運用していると思う。本番環境において dbt_project_evaluator のモデルは基本的に不要だと思うので、生成されないようにしたくなると思う。
この対応として、主に以下の2つのアプローチが考えられる。
1. 実行コマンドで制御する
手っ取り早いのは dbt run --select models/* で models/ 配下の dbt モデルのみを対象とするよう指定する方法。
2. dbt_project.yml で制御する
dbt_project.yml でパッケージ側の enabled 設定を target.name によって切り替えることでも対応できる。
models:
dbt_project_evaluator:
+enabled: "{{ target.name != 'prod' }}" # prod環境では無効にしておく
marts:
dag:
+enabled: false
fct_hard_coded_references:
+enabled: "{{ target.name != 'prod' }}"
documentation:
+enabled: false
governance:
+enabled: false
performance:
+enabled: false
structure:
+enabled: false
tests:
+enabled: false
ただ、有効にしたいルールに都度 "{{ target.name != 'prod' }}" を書かないといけないので、あまり良い方法ではない気がしている…。