dataformのassertions周りのアップデート
dataform-coreのアップデート
まだβ版だが、dataformでは2.Xから3.0へのメジャーバージョンアップが進んでいる。
設定ファイルがjsonからyamlに変更されるなど色々な変更があるようだが、その中でもassertion関連のリリースが目を引いたので、周辺パッケージのアップデートも含めてこの記事で紹介したい。
3.0では、dependOnDependencyAssertions
とincludeDependentAssertions
という2つのオプションが追加されている。
このオプションが追加された背景を説明すると、dataformではdbtのbuildコマンドとは異なり、デフォルトではテストが失敗しても、後続処理のモデルはそのまま実行されていた。即ち、初期設定ではモデルの作成処理のみが依存関係になっている。
テストが失敗した場合に後続モデルが作成されないようにするには、都度テストごとにdependencies
に依存関係を追加する必要があった。例えば、first_viewがtest1~5のassertionを持つ場合は以下のようになる。
config {
type: "view",
dependencies: ["test1", "test2", "test3", "test4", "test5"]
}
SELECT
test
FROM
${ref("first_view")}
重要なデータマートでは、テストが5件以上あることも珍しくない。このような場合、dependencies
にテストを都度追加するのはかなり面倒である。さらに、テストを追加したときにdependencies
側を修正し忘れるリスクも高まる。
そこで登場したのがdependOnDependencyAssertions
オプションである。configブロック内で有効にすると、refで参照するモデルとdependencies
で定義するモデルすべてのassertionに対して依存関係が作られ、すべてのassertionが成功した場合にのみ実行されるようになった。
config {
type: "view",
dependOnDependencyAssertions: true,
}
SELECT
test
FROM
${ref("first_view")}
コードがシンプルになったのが一目でわかる。もう1つのincludeDependentAssertions
については、以下のようにrefとdependencies両方に設定できるが、nameで指定したモデルのassertionsを対象に、依存関係として定義できる。こちらは特定のモデルに対してだけassertionの依存関係を設定したいときに使うようだ。
config {
type: "view",
dependencies: [{name: "some_table", includeDependentAssertions: true}]
}
select test from ${ref({name: "some_other_table", includeDependentAssertions: true})}
この場合は、some_table
とsome_other_table
のassertionがすべて成功した場合にのみこのモデルは実行されるようになる。
不満があるとすれば、dependency/dependentといった似た単語がdataformの用語としてテスト周りだけでなく全体に多用されているところだろうか。未だに混乱することがあるので、できればupstream/ancestorsなど直感的なワードを採用して欲しいところである[1]。
dataform-assertionsの機能追加
dataformでリッチなテストを簡単に実装できるdataform-assertionsパッケージについては、前回の記事で触れたが、こちらのパッケージにもいくつか機能が追加された、というか一部は自分で実装してPRを送ってみたところマージしてもらえた。
source freshnessの範囲が拡張
- 比較対象のカラムにTIEMSTAMP型が対応し、SECOND, MINUTE, HOURのようなより細かい単位での比較が可能に
- date型の場合はtime zoneを指定できるように変更
元々date型がベースになっていることもあって、dbtのsource freshnessよりもリッチな仕様になっている。
dataFreshnessConditions: {
"first_table": {
"dateColumn": "updated_date",
"timeUnit": "DAY",
"delayCondition": 1,
"timeZone": "America/Los_Angeles"
},
"second_table": {
"dateColumn": "TIMESTAMP(updated_date)",
"timeUnit": "HOUR",
"delayCondition": 3,
}
},
configブロックでテストする範囲を指定できるように
この機能は、テスト対象がTB級のサイズで一部のデータしか更新されないテーブルであったとき、テスト対象を絞り込みスキャン量を減らしたい場合に便利である。変化していないデータで過去にテストが成功しているなら、再度テストを行う意味はないからだ。
dbtではcoreに実装されており、configブロックを使ってテスト範囲をテストごとに指定できる。
version: 2
models:
- name: large_table
columns:
- name: my_column
tests:
- accepted_values:
values: ["a", "b", "c"]
config:
where: "created_at > CURRENT_DATE() - 7"
一方、dataformにはこれまで同等の機能はなく、カスタムテストを書くか、テスト用のモデルを個別に作るしかない状態だった。そのため、dbtのコンセプトをそのままこのパッケージにも実装した。
const commonAssertions = require("../index");
const commonAssertionsResult = commonAssertions({
globalAssertionsParams: {
"database": {project},
"schema": "assertions_" + dataform.projectConfig.vars.env,
"location": "EU",
"tags": ["assertions"],
},
// この階層にテーブル単位で指定する
config: {
"first_table": {
"where": "updated_date >= CURRENT_DATE() - 7"
},
},
rowConditions: {
"first_table": {
"id_not_null": "id IS NOT NULL",
"id_strict_positive": "id > 0"
},
"second_table": {
"id_in_accepted_values": "id IN (1, 2, 3)"
}
},
...
id_not_nullのテストの場合、以下のようなクエリになる。事前にテーブルをフィルタリングしてから本来の条件式が実行される。
WITH
filtering AS (
SELECT
*
FROM
`{project}.dataform.first_table`
WHERE
updated_date >= CURRENT_DATE() - 7
)
SELECT "Condition not met: id IS NOT NULL, Table: `{project}.dataform.first_table`" AS assertion_description
FROM filtering
WHERE NOT (id IS NOT NULL)
dataform-assertionsもまだ小さいパッケージなので、こういった機能追加もかなり短時間で実装できた。機能追加できる余地はまだまだあるので、dataformを利用されている方、OSSに関わってみたい方は是非contributeしてみて欲しい。
-
主体からの矢印の向きで考えると覚えやすい。A→B→Cというデータパイプラインがあったとき、Bから見ると、dependencyは「主体が依存している」という意味なので、A←Bの方向。dependentは「主体が依存されている」という意味なので、B←Cの方向である。ちなみに日本語版のドキュメントでは「依存」と「依存関係」という直訳で翻訳が放棄されている。 ↩︎
Discussion