「契約テストとdbtを用いたデータパイプラインおよびデータ製品の効果的なスケーリングに関する完全ガイド」

「契約テストとDBTを活用したデータパイプラインとデータ製品の効果的なスケーリング完全ガイド」

dbtを使用して契約テストを実装するために知っておく必要があるすべて

Photo by Jonas Gerg on Unsplash

2023年に最善の仕事をしようとするデータまたは分析エンジニアの場合、データ管理システムとスケールに関する物語をお伝えしましょう。この物語はおそらく共感することでしょう。

それほど遠くない昔、ほとんどのデータアーキテクチャとデータチームの構造は集中的なアプローチに従っていました。 データまたは分析エンジニアとして、すべての変換ロジックとモデルを見つけることができました。なぜなら、それらはすべて同じコードベースにありました。おそらく、それを消費しているデータパイプラインを構築している同僚と密接に連携して働いていました。データチームは1つだけ、最大で2つありました。

このアプローチは、データソースとユースケースが限られた小規模な組織やスタートアップには効果的でした。データから価値を抽出することに完全に集中していない大企業でも機能しました。しかし、データ駆動を優先した組織では、さらに機械学習、分析、ビジネスインテリジェンスのデータユースケースが必要とされるようになりました。

Centralized data architecture developed and maintained by one team

ユースケースとデータソースの拡大により、データの管理とデータシステムの作成およびメンテナンスに必要な人員の数が増加し、複雑さが増しました。これらのニーズに対応するために、会社のデータ戦略の最新バージョンでは、分散化に向けて進化しているかもしれません。これには、分散化されたデータチームの形成とData Meshのような分散化されたデータアーキテクチャの採用が含まれます。

分散化により、組織はデータの管理をスケールアップすることができますが、データ製品とパイプラインの調整を確保する新たな課題が発生します。これは、さまざまなチームによって開発および管理されるデータ製品やパイプライン間の統合ポイントの数が増加し、異なるコンポーネント間の作業インターフェースを維持することがより困難になるためです。

Decentralized data architecture with multiple teams and multiple components

このような状況に共感できるのなら、孤立しているわけではありません。あなたの組織はデータの分散化を経験しているかもしれません。この移行を進めるために、オペレーションの世界での分散化および分散アーキテクチャ(マイクロサービスなど)の成功した実装から学ぶことができます。それらはどのようにして実現することができましたか?そのスケールで信頼性のあるシステムを提供することができたのはなぜですか?それは近代的なテスト技術を活用したからです。

「長年にわたって、ソフトウェアエンジニアリングは「二つのピザのチーム」によって行われる小さな単位の作業コンセプトを成功裏に受け入れてきました。各チームは大きなシステムの一部を所有します。チームは、明確に定義されたバージョン管理されたインターフェースを通じて相互に統合します。残念なことに、データはまだソフトウェアに追いついていません。モノリシックなデータアーキテクチャはまだ一般的ですが、明確な欠点があります。」— dbt labs

この記事では、そのようなテクニックの1つである契約テストを紹介します。私はdbtを使用して、上流ソースとdbtモデルの公開インターフェースに対してシンプルな契約テストを作成する方法を示します。このタイプのテストは、dbtアプリがより複雑化し、分散化するにつれて、あなたを正気に保つでしょう。

しかし… 契約テストとは何ですか?

分散システムが複数のチームによって開発される複数のコンポーネントへと成長すると、システムが期待どおりに動作しているかを検証するためにチームが最初に試みるアプローチは、システム全体を実行するエンドツーエンドテストを実装することです

システム全体を検証するエンドツーエンドテストの範囲に焦点を当てたテスト

エンドツーエンドテストは、複雑さ、フィードバックの遅さ、およびメンテナンスと組み合わせる難しさにより、非常に扱いにくくなることがよくあります。

これは、マイクロサービスを大規模に実装する際の運用ワールドでの事例でした。システム全体をテストすることができない場合、エンジニアリングチームは契約テストなど、さまざまなアプローチを実装し始めました。

「統合契約テストは、消費サービスが期待する契約を満たす外部サービスの境界でのテストです。」— トビー・クレムソン

チームは、エンドツーエンドテストの小さなセットを保持することはできますが、契約、コンポーネント、およびユニットテストのような高速かつ信頼性の高いテストによってシステムを検証することで、テストピラミッドを下に移動させます。

異なるテストタイプのトレードオフは、テストピラミッドで視覚化されることがよくあります。私は以前の記事でこのコンセプトについて言及しました。 dbtモデルのユニットテストの実装について

運用システムにおける典型的なテストピラミッド

同じ概念をデータ管理システムに適用すると、dbtアプリの契約テストは、次の2つのインターフェースの動作を検証するために実装できます:

  • 上流ソース。
  • マートや出力ポートなどのパブリックインターフェース。
契約テストの範囲

データシステムの契約テストの利点

データアーキテクチャは、運用サービスのように以前と同様により複雑で分散化しています。このタイプのシステムがスケーリングし続けると、実行可能で効果的なエンドツーエンドテストスイートを実行する能力は低下します。

契約テストは、さまざまな状況を管理するための強力な協力者となり、次のようないくつかの利点を提供します:

  • システムの動作を検証するために必要なエンドツーエンドテストの数を減らすこと。これにより、より速いフィードバックとメンテナンスコストの削減が可能となります。
  • 同じコードベースで別のデータチームが作業するという複雑さを管理し、チーム間の公開インターフェース間の明確な期待を提供すること。
  • 本番環境に到達する前に、コンポーネント間の統合問題を低レベルの環境で浮かび上がらせること。
  • 異なるデータパイプラインまたはデータ製品間の明確で文書化されたインターフェース。

契約テストとデータ品質テスト

おそらく、「しかし…契約テストの概念は、すでにデータパイプラインで実行している品質テストと似ているように思えるのですが」と思っているかもしれません。

それは公平な観察です。契約テストとデータ品質テストの範囲には、あいまいな境界線が存在します。私は契約テストを、現代のデータテスト戦略の一部として、品質テストのサブセットと考えることが好きです。

契約テストは品質テストのサブセットと見なすことができます

違いは、契約テストはスキーマと制約を確認し、データの品質テストは実際のデータとその特性を確認することです。いくつかの例を見てみましょう。

契約テストの範囲:

  • 列の型をチェックします。
  • スキーマレベルでの主キーと外部キー、非NULL列などの期待される制約をチェックします。
  • 指定した列の受け入れられる値をチェックします。
  • 指定した列の有効な範囲をチェックします。

品質テストの範囲:

  • 完全性を評価する、例えば列の非NULLの割合。
  • 一意性を評価する、例えば一意でない行の数。
  • 一貫性を評価する、例えばソースのすべてのユーザー識別子が出力に含まれていること。

最初の契約テストの実装

理論は十分です、簡単な例で実際の行動に移りましょう。私達にはhealth-insightsというdbtアプリがあり、上流データソースから体重と身長データを取得し、体格指数を計算します。

素晴らしいバックエンドチームの同僚が、私達のhealth-insightsアプリを構築するために必要な体重と身長データの生成を担当しています。彼らは多忙でストレスがあり、時々スキーマの変更を私達に通知しそこねることがあります。上流インターフェースのこれらの変更に対してテストするために、私達は最初のソース契約テストを作成することにしました。

The system architecture of our example

まず、スキーマと受け入れられる値に対してアサーションを行うための2つの新しいdbtパッケージ、dbt-expectationsとdbt-utilsを追加する必要があります。

# packages.ymlpackages:  - package: dbt-labs/dbt_utils    version: 1.1.1  - package: calogica/dbt_expectations    version: 0.8.5

データソースのテスト

最初のソースに対して契約テストを定義することから始めましょう。私達はジムアプリのユーザーの身長情報を含むraw_heightというテーブルからデータを取得しています。

私達のデータプロデューサーと合意して、身長の測定値、測定値の単位、およびユーザーIDを受け取ることになりました。データ型として受け入れられる値としては、’cm’と’inches’のみがサポートされると合意しました。これらすべてを考慮し、dbtソースYAMLファイルに最初の契約を定義できます。

構築ブロック

前のテストを見ると、dbt-unit-testingのいくつかのマクロが使用されていることがわかります:

  • dbt_expectations.expect_column_values_to_be_of_type: このアサーションを使用すると、期待される列のデータ型を定義できます。
  • accepted_values: このアサーションを使用すると、特定の列の受け入れられる値のリストを定義できます。
  • dbt_utils.accepted_range: このアサーションを使用すると、指定された列の数値の範囲を定義できます。この例では、列の値が0未満でないことを期待しています。
  • not null: 最後に、’not null’のような組み込みのアサーションを使用すると、列の制約を定義できます。

これらの構築ブロックを使用して、上記で説明した契約の期待値を定義するためにいくつかのテストを追加しました。また、テストを「contract-test-source」というタグでタグ付けしたことにも注目してください。このタグにより、すべての契約テストを独立して実行できます。これは、ローカルで実行することも、後で見るようにCI/CDパイプラインで実行することもできます:

dbt test --select tag:contract-test-source

マートと出力ポートの契約テストの実装

dbtアプリのソースに対して契約テストを迅速に作成する方法を見てきましたが、データパイプラインやデータ製品の公開インターフェースはどうでしょうか。

データプロデューサーとして、データ消費者の期待に従ってデータを生成して、契約を満たし、データパイプラインやデータ製品を信頼性のあるものにしたいと思います。

<!–私たちがデータ消費者に対する義務を果たしているかどうかを確認する簡単な方法は、パブリックインターフェースに契約テストを追加することです。

最近、DbtはSQLモデル用の新機能であるモデル契約をリリースしました。これにより、dbtモデルの契約を定義することができます。モデルを構築する際、dbtはモデルの変換が契約に一致するデータセットを生成するかどうかを確認し、生成に失敗することがあります。

それを実際に見てみましょう。私たちのマートである体格指数(body_mass_indexes)は、ソースから得た体重と身長のデータからBMI指標を作り出します。サプライヤーとの契約では以下のように定められています:

  • 各列のデータ型
  • ユーザーIDはnullではない
  • ユーザーIDは常に0より大きい

dbtモデル契約を使用してbody_mass_indexesモデルの契約を定義しましょう:

構成要素

前のモデル仕様ファイルを見ると、契約を定義するためのいくつかのメタデータがわかります。

  • contract.enforced: この設定は、dbtがモデルを実行するたびに契約を強制することをdbtに伝えます。
  • data_type: このアサーションでは、モデルが実行された後に期待される列の型を定義できます。
  • constraints: 最後に、constraintsブロックでは、列がnullではないこと、プライマリキーを設定すること、およびカスタム式を定義するなど、便利な制約を定義する機会が与えられます。上記の例では、user_idが常に0より大きいことをdbtに伝える制約を定義しました。使用可能な制約はこちらをご覧ください。

データソースのために定義された契約テストとマートまたは出力ポートのために定義された契約テストの違いは、契約が検証されるタイミングと強制されるタイミングです。

モデル契約は、dbt runによってモデルが生成される際に強制されますが、dbtテストに基づく契約は、dbtテストが実行される際に強制されます。

モデル契約のいずれかが満たされていない場合、特定の詳細な失敗に関する情報が表示される「dbt run」コマンドの実行時にエラーが表示されます。以下の「dbt run」コンソール出力の例をご覧ください。

1 of 4 START sql table model dbt_testing_example.stg_gym_app__height ........... [RUN]2 of 4 START sql table model dbt_testing_example.stg_gym_app__weight ........... [RUN]2 of 4 OK created sql table model dbt_testing_example.stg_gym_app__weight ...... [SELECT 4 in 0.88s]1 of 4 OK created sql table model dbt_testing_example.stg_gym_app__height ...... [SELECT 4 in 0.92s]3 of 4 START sql table model dbt_testing_example.int_weight_measurements_with_latest_height  [RUN]3 of 4 OK created sql table model dbt_testing_example.int_weight_measurements_with_latest_height  [SELECT 4 in 0.96s]4 of 4 START sql table model dbt_testing_example.body_mass_indexes ............. [RUN]4 of 4 ERROR creating sql table model dbt_testing_example.body_mass_indexes .... [ERROR in 0.77s]Finished running 4 table models in 0 hours 0 minutes and 6.28 seconds (6.28s).Completed with 1 error and 0 warnings:Database Error in model body_mass_indexes (models/marts/body_mass_indexes.sql)  new row for relation "body_mass_indexes__dbt_tmp" violates check constraint   "body_mass_indexes__dbt_tmp_user_id_check1"  DETAIL:  Failing row contains (1, 2009-07-01, 82.5, null, null).  compiled Code at target/run/dbt_testing_example/models/marts/body_mass_indexes.sql

パイプラインでの契約テストの実行

これまで強力な契約テストのテストスイートを持っていますが、いつ、どのようにしてそれらを実行しますか?

契約テストを2種類のパイプラインで実行できます。

  • CI/CDパイプライン
  • データパイプライン

たとえば、CI/CDパイプラインで定期的にソースの契約テストを実行し、テストまたはステージングなどの下位環境で利用可能なデータソースを対象にすることができます。契約が満たされない場合、パイプラインを失敗させることができます。

これらの失敗は、これらの変更が本番環境に到達する前に他のチームが導入した契約違反の変更に関する貴重な情報を提供します。

Github Actionsでのdbt CI/CDパイプラインの例

また、CI/CDパイプラインを介して新しい変更をデプロイするたびに、出力ポート/マートの契約テストも実行することができます。 dbtモデルの契約は、モデルが構築されるたびにチェックされるため、新しいモデルの変更が契約の破棄を引き起こす場合、データの利用者が影響を受ける前にチームに通知されます。

最後に、本番環境のデータパイプラインでも、ソースと出力ポート/マートの契約テストを実行することもできます。本番環境で契約テストを実行すると、データパイプラインが上流の依存関係が契約を破ったために失敗したのか、それとも生成されるデータが下流の利用者との契約を満たしていないために失敗したのかが、チームが把握できます。

はじめるための追加のヒント

  • まずはじめに、もっとも壊れやすく失敗しやすい統合ポイントのテストを行ってください。
  • 契約テストを実装する際には、容認リーダーパターンを適用してください。必要なデータだけにアサートしてください。
  • 必要に応じて契約テストの振る舞いを調整します。重要度の属性を設定することで、テストが失敗した場合に大きなエラーとして表示させるか、警告として表示させるかを指定できます。
  • これらのテストをMontecarloのような現代のデータオブザーバビリティツールと統合し、インシデント管理プロセスの一部にします。
  • dbtの契約テストを利用して、dbtを使用していないデータシステムでも活用することができます。他のフレームワークやプレーンなSQLで作成されたテーブルやファイルに対して、dbtでソースの契約テストを定義し、実行することができます。
  • コンシューマーによる契約などのより高度な契約テスト技術を検討すると、特定のコンテキストで契約テストを容易に実装できるようになります。

まとめ

データシステムのテスト戦略も、より分散化し複雑になるにつれて、契約テストなどのテスト手法からの恩恵を受けることができます。

また、dbtの組み込み機能や追加のdbtパッケージを活用して、契約テストの実装を始める方法を見ました。アップストリームのデータソースやデータマート/出力ポートの2つの統合ポイントに対して、このタイプのテストを適用しました。

この記事が、データシステムが新しいデータ利用ケースを満たすためにスケールするにつれて、チームに契約テストの実装に役立つツールやヒントを提供できれば幸いです。興味があれば、このGithubリポジトリの例のdbtアプリケーションのソースコードをチェックしてみてください。

契約テストの導入に挑戦して、あなたの契約テストの旅を始める準備はできていますか?ご意見や経験をコメントでお聞かせください。

次回の記事では、データシステムのテストシリーズにおける欠落している要素であるデータ品質テストと、そのdbtでの実装方法について話します。

この記事の初期バージョンをレビューしてくれたThoughtworksの同僚、ArneさんとManishaさんに感謝します。また、dbt-expectationsパッケージのメンテナーの方々にも感謝します。

著者による、他の特記事項はすべてイメージです。

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more