
dbt Cloudでドキュメントに記載のあるテストオプションを試してみた
かわばたです。
dbtテストの各オプションについてどのようなことが可能か、dbt Cloudで検証してみました。
対象読者
- dbtテストの各オプションについて確認したい方
検証環境と事前準備
検証環境
- dbt CloudのEnterprise版
- SnowflakeトライアルアカウントのEnterprise版
事前準備
検証用のデータ
test時にエラーとして確認したいため、重複や欠損をわざと作成しています。
raw_customers.csv
customer_id,first_name,last_name,status
1,Michael,Scott,active
2,Dwight,Schrute,active
3,Jim,Halpert,inactive
4,Pam,Beesly,active
4,Andy,Bernard,inactive
,Angela,Martin,active
6,Kevin,Malone,shipped
raw_orders.csv
order_id,customer_id,order_date,amount
1,1,2024-01-01,100
2,1,2024-01-15,150
3,2,2024-01-05,200
4,3,2024-01-20,50
5,4,2024-02-01,75
6,4,2024-02-10,125
7,99,2024-02-15,25
8,2,2024-02-20,-50
これらをseed
フォルダへ格納しています。
また、テストを行うためにrawデータから簡易なモデルをstaging
フォルダに作成しています。
※テストもstaging
レイヤーで実施しますが、後述します。
stg_customers.sql
select
customer_id,
first_name,
last_name,
status
from {{ ref('raw_customers') }}
stg_orders.sql
select
order_id,
customer_id,
order_date,
amount
from {{ ref('raw_orders') }}
後続の処理を確認するため、mart
フォルダに簡易なモデルを作成します。
mart.sql
select
o.order_id,
o.customer_id,
o.order_date,
o.amount,
c.first_name,
c.last_name,
c.status
from {{ ref('stg_orders') }} AS o
left join {{ ref('stg_customers') }} AS c ON o.customer_id = c.customer_id
dbtテストのオプション確認
dbtのテストは、configブロックを使用することで、テストの挙動を細かく制御することができます。
各種設定オプションについて以下にまとめました。
詳細はドキュメントをご確認ください。
設定 | 設定可能な値 | デフォルト値 | 目的 | ユースケース |
---|---|---|---|---|
fail_calc | SQLの集計式 | count( * ) | テストの失敗数を計算する方法をカスタマイズする。 | 単純な行数ではなく、特定のビジネス指標(例:金額の差分)に基づいてテストの成否を判定したい場合に使用する。 |
severity | error, warn | error | テスト失敗時の影響度を定義する。 | warnは、パイプラインを停止させたくないが監視は必要な軽微なデータ品質問題(例:推奨フィールドの欠損)に使用する。errorは、パイプラインを即時停止させるべき重大な問題(例:主キーの重複)に使用する。 |
store_failures | true, false | false | テストに失敗したレコードをデータベースに保存する。 | デバッグと原因分析を容易にするために使用する。失敗した具体的なレコードをデータオーナーやソースシステムの管理者に提示し、修正を依頼する際に非常に有効。 |
store_failures_as | table, view, ephemeral | ephemeral | 失敗レコードの保存形式を指定する。 | tableは永続的な監査証跡として、viewは常に最新の失敗状況を確認したい場合に使用する。store_failuresよりも優先される。 |
where | SQLのWHERE句の条件式 | なし | テスト対象となるレコードをフィルタリングする。 | 増分モデルの新規データ部分のみをテストしてパフォーマンスを向上させたり、特定のビジネス条件下(例:「有効な」ユーザーのみ)でテストを適用したりする。 |
limit | 整数 | なし | テストクエリが返す失敗レコードの最大数を制限する。 | テストが大量のレコードを返す可能性がある場合に、パフォーマンスの低下を防ぎ、データベースへの負荷を軽減するために使用する。 |
error_if | 比較式 (例:>100) | != 0 | errorと判定される失敗レコード数の閾値をカスタマイズする。 | 少数のエラー(例:10件未満の重複)は警告(warn)とし、多数のエラー(例:100件以上)が発生した場合にのみパイプラインを停止(error)させたい場合に使用する。 |
warn_if | 比較式 (例: > 10) | != 0 | warnと判定される失敗レコード数の閾値をカスタマイズする。 | error_ifと組み合わせて、エラーレベルを段階的に設定する。例えば、「10件を超えたら警告、100件を超えたらエラー」といったルールを定義する。 |
実際に検証
テストの作成
今回はstaging
レイヤーでテストを実施したいため、staging
フォルダ内にschema.yml
を作成しました。
version: 2
models:
- name: stg_customers
columns:
- name: customer_id
description: "顧客の一意の識別"
tests:
# 'warn_if', 'error_if', 'limit' のデモ
- unique:
config:
# 1件の重複は警告とし、2件以上でエラーとする
warn_if: "= 1"
error_if: "> 1"
# パフォーマンスのため、失敗レコードを1件に制限
limit: 1
# 'where' のデモ
- not_null:
config:
# 'active'ステータスの顧客にのみ非NULL制約を適用
where: "status = 'active'"
- name: status
description: "顧客アカウントのステータス"
tests:
# 'severity' のデモ
- accepted_values:
values: ['active', 'inactive']
config:
# このテストが失敗してもパイプラインを止めず、警告のみを出す
severity: warn
- name: stg_orders
columns:
- name: customer_id
description: "顧客テーブルへの外部キー"
tests:
# 'store_failures'と'store_failures_as'のデモ
- relationships:
to: ref('stg_customers')
field: customer_id
config:
# --store-failures フラグ付きで実行した場合に失敗レコードを保存
store_failures: true
store_failures_as: view
1つのymlファイル内にテストを一括管理もできますが、各層・各モデルで定義をした方が修正ミスが減らせるのではないかを考えています。メリット・デメリットあるので組織の活用用途に応じて確認ください。
テスト管理方法 | メリット | デメリット |
---|---|---|
一括管理 | ファイル数が少ない | テストを一括管理するので、ymlファイル内が煩雑になる |
モデルごとに管理 | モデルとテストが対応していて修正ミスが減らせる | モデル増加ごとにテストファイルも増えるのでファイル管理を検討が必要 |
テストの実行
実際にdbt build
を行っていきます。
Passが4件、Warnが2件、Errorが2件、Skipが1件でした。順に確認していきます。
-
Pass
seed
とstaging
のモデルは通っています。
-
Warn
- accepted_values_stg_customers_status__active__inactive
- このテストは
status
カラムに不正な値が存在するため失敗します。ただ、severity
がwarn
に設定されているため、実行結果のステータスWARN
となります。
- このテストは
- unique_stg_customers_customer_id
- このテストは
customer_id
の重複を検出します。limit: 1
が設定されているため、dbtが生成するテストクエリの末尾にlimit 1
句が追加されます。もし何千もの重複があったとしても、この設定によりパフォーマンスの低下を防ぎます。 warn_if: "= 1"
の条件に一致するため、テストのステータスはFAIL
ではなくWARN
になります。もし重複が2件以上あれば、error_if: "> 1"
の条件に一致し、ステータスはFAIL
となります。
- このテストは
- accepted_values_stg_customers_status__active__inactive
- Error
- not_null_stg_customers_customer_id
where: "status = 'active'"
句によって、status
がactive
のレコードのみを対象とします。status
がactive
のレコードでcustomer_id
がNULL
のレコードが存在するためError
となります。
- relationships_stg_orders_customer_id__customer_id__ref_stg_customers_
stg_orders
に存在するcustomer_id: 99
がstg_customers
に存在しないためFAIL
します。store_failures_as
の方が優先されるためview
としてエラー箇所が保存されます。
- not_null_stg_customers_customer_id
store_failures_as
によってエラー箇所がview
としてSnowflakeに保存されていることが確認できました。
store_failures
でtrue
(テーブルで保存)としてますが、store_failures_as
が優先されていることが分かります。
合わせてSnowflakeを確認すると、seed
とstaging
のモデルは作成されていましたが、'mart.sql'は作成されていませんでした。
fail_calcのテスト
fail_calc
のテストも実行します。
下記のようにschema.yml
を編集し、テストを作成します。
version: 2
models:
- name: stg_customers
columns:
- name: customer_id
description: "The unique identifier for a customer."
tests:
# 'fail_calc' のデモ
- unique:
config:
# 失敗の計算方法を「重複している値の数」から「重複しているレコードの総数」に変更
fail_calc: "case when count(*) > 0 then sum(n_records) else 0 end"
# fail_calcの変更により、失敗数が2になるため、WARNになる
error_if: "< 2"
warn_if: ">= 2"
再び、dbt build
を実行し、下記のように警告となりました。
- Warn_fail_calc
- unique_stg_customers_customer_id
- デフォルトでは、テストの失敗数は失敗したレコードのグループ数(count(*))で計算されます。
customer_id: 4
の重複は1つのグループなので、失敗数は1です。しかし、fail_calc
をsum(n_records)
に設定することで、失敗の計算方法を「重複しているレコードの総数」に変更しました。customer_id: 4
のレコードは2つあるため、失敗数は2になります。この変更により、warn_if: ">= 2"
の条件が満たされ、テストのステータスはFAIL
ではなくWARN
として表示されます。
- デフォルトでは、テストの失敗数は失敗したレコードのグループ数(count(*))で計算されます。
- unique_stg_customers_customer_id
Warnだけでテスト実行
実際にWarn
となった場合、後続のmart.sql
モデルが作成されるのか確認していきます。
先ほどテストを記載していたschema.yml
を編集し、エラーとなっていた箇所をコメントアウトして保存します。
version: 2
models:
- name: stg_customers
columns:
- name: customer_id
description: "顧客の一意の識別"
tests:
# 'warn_if', 'error_if', 'limit' のデモ
- unique:
config:
# 1件の重複は警告とし、2件以上でエラーとする
warn_if: "= 1"
error_if: "> 1"
# パフォーマンスのため、失敗レコードを1件に制限
limit: 1
# 'where' のデモ
# - not_null:
# config:
# # 'active'ステータスの顧客にのみ非NULL制約を適用
# where: "status = 'active'"
- name: status
description: "顧客アカウントのステータス"
tests:
# 'severity' のデモ
- accepted_values:
values: ['active', 'inactive']
config:
# このテストが失敗してもパイプラインを止めず、警告のみを出す
severity: warn
再び、dbt build
を実行します。
想定通り、mart.sql
モデルまで実行できており、コマンド自体もSuccess
となっています。
Snowflake側も確認し、反映されていることが確認できました。
最後に
dbtテストの各オプションについて確認しました。
store_failures
はテスト失敗レコードの保存を有効にするかどうかの設定で、store_failures_as
は保存形式(テーブルやビュー)を指定します。store_failures_as
を設定すれば自動的に失敗レコードが保存されるため、基本的には store_failures_as
を使用するほうが良いかなと考えています。
※個人的にエラー箇所が保存されるのは、リカバリを実施する際に調査が楽になると感じました。
また、Warn
とError
の制御ですが、基本的にはError
としてパイプラインを止める形が良いと考えています。理由としては、データが正しくない状態で後続処理をしてしまい結果として手戻りが発生してしまう可能性が高いためです。
こちらの記事が参考になれば幸いです!