dbt test をキチンと実装するにはドキュメントを書くことが一番の近道だと思ってる話

2023-10-13

はじめに

Data Platform チーム所属の dach です。

データ分析基盤は、データを利活用してもらうことで初めて価値が出ます。ユーザーに継続的にデータを利活用してもらうためには、提供しているデータへの信頼が欠かせません。データへの信頼が失われた場合、次第にユーザーは提供しているデータを利用しなくなり、データ分析基盤としての存在価値を失うことは想像に難くないことかと思います。そのため、データの品質を担保していくことはデータ分析基盤として重要なミッションだと言えます。 dbt ではデフォルトの機能として、データ品質に対するテスト機能が提供されてます。しかし、闇雲にテスト実装を行ったとしても、本当に確認したいことが確認できてなければデータ品質は保たれません。キチンとした品質を保つためには何ができるかという手段に囚われず、何故このテストを行うのかという目的を持ってテストすることが重要です。

本記事ではデータ品質を維持するために、データの理解をすることの重要性とデータについての説明をドキュメントに書くことがデータの理解に最適な方法である、という私見について紹介します。

データに対して適切なテストを行う

dbt test について学ぶ

dbt ではデータ品質に対するテストを行う機能を提供しています1。実装の詳細な説明は省きますが、yml 上に以下のように記述することで、モデルやカラムを対象にテストが指定できます。テストを実行したい場合は dbt test コマンドを実行することで、設定したテストを実行するクエリを dbt が自動で生成 & 実行します。

version: 2

models:
  - name: orders
    columns:
      - name: order_id
        tests:
          - unique
          - not_null
      - name: product
        tests:
          - relationships:
              to: ref('products')
              field: name
      - name: price
        tests:
          - relationships:
              to: ref('products')
              field: regular_price

テストの仕組みとしてはシンプルで、生成済みモデルに対してテストクエリを実行し、実行結果がないことをアサーションするということを行っています。各モデル上に期待しない結果が入っていないことを確認することで、逆説的に期待通りの実装がされているかを確認しています。

dbt test には、事前定義されたマクロを利用する generic test と、自分でテストクエリを実装する singular test の 2 種類が存在していますが、どちらもデータ品質に対するテストであるということは変わりません。 また、dbt には dbt-expectationsdbt-utils と言った test を拡張する便利なライブラリがありますが、これらも選択できる手数が増えるだけであり、テストの目的は変わりません。

データの理解が何故重要なのか

データの理解が何故重要なのか結論から述べると、データの理解はユーザーに提供するデータを作るロジックに直結するからです。どんなデータを提供すればいいか分からないのであれば、参照すべきデータも計算方法やクレンジングや標準化などについても決めることが出来ないため、テストを作るばかりかそもそもモデルの実装すらできない筈です。仮にそのような状態で実装ができているのであれば、間違ったものを提供しているかユーザーに使われていない可能性が高いです。適切なデータを提供するためにも、まずは、そもそも提供するデータとは一体何者であるか、ということの理解を行うことが重要であると考えます。

データの理解が重要であることについて、例を用いてもう少しだけ理解を深めていきます。

あなたはデータ分析基盤のエンジニアです。 ユーザーから「商品単位での受注件数と平均取引価格の傾向を見たい」という要望があったとします。あなたのデータ分析基盤には受注情報を記録する orders という DataSource から抽出した生データを記録しているテーブルを持っています。orders テーブルには、product というカラムと price というカラムがあるため、このテーブルを使えばユーザーからの要望を叶えられると思ったあなたは、 orders テーブルを product カラムで group by し、受注件数として count(1) した結果を使い、平均取引価格として avg(price) を出しました。しかし、後になってユーザーから「実際の計上金額とズレが有る」との指摘をもらいました。一体何故でしょうか? 実は、price カラムは商品を販売した時の定価が記録されているだけで、クーポンやセールで割引された後の実際の取引額とは違っていたことが分かりました。実際の取引額を出すためには別のカラムと合わせて計算する必要があったのです。また、受注後にキャンセルしたケースも計算してしまっていることも分かりました。ユーザーは計上されたものだけを見たかったので、キャンセルした分も含めてしまうと更に算出結果がズレてしまいます。

仮に、出力した平均取引価格に対し、適切な集計軸で平均値が取れていることを確認するテストを実装していたとしても、この問題には気づけません。そもそも取引価格として使うカラムの認識が違っているから、テスト自体も間違った前提のもとで組まれているからです。この問題を防ぐためには、データの理解が重要です。もし orders テーブルだけではキャンセル分は弾けないことや price というカラムだけでは取引価格は算出出来ないことがわかっていればどうでしょうか。少なくとも、モデルの実装時点かテストの実装時点で気づくことが出来たかもしれません。もちろんカラムにつけられている名称が悪いなどの意見もありますが、絶対に誤解されない名前が必ずつけれられている保証はどこにもありません。ですが、データについての説明がドキュメントにあれば、名前だけに頼らずに誰でもどんなデータが含まれているのかを理解することが出来ます。そしてその情報があれば、データ品質を担保する適切なテストを記述することも可能です。また、データについての説明をドキュメントに書くということは、データを理解するということに繋がります。なので私は、データの理解を適切にするためにも、データについての説明をドキュメントに書くことが重要だと考えます。

データについての説明をドキュメントに書く

dbt にはモデルに関するドキュメントを生成する素敵な機能があります2。yml 上に記述することで、モデルやカラムに説明を書くことが出来ます。ドキュメントを生成したい場合は dbt docs generate コマンドを実行することで、html ファイルが生成されます。また、dbt docs serve を実行すると、local server で確認することも出来ます。記載した説明をモデルを生成する DB へ反映させることも出来ます3

version: 2

models:
  - name: orders
    description: |
      Service A から取得した生の受注データ。
      Service A は toC Service かつ単品での取引が主であるため、受注記録は1ユーザーかつ1商品単位で記録される。
      このデータはキャンセルした分や受注後に変更があったデータも全てイミュータブルに記録している。
      [関連するモデル] 
          - order_cancel
          - coupon
    columns:
      - name: order_id
        description: 顧客からの受注単位でつけれられる識別子
        tests:
          - unique
          - not_null
      - name: product
        description: 受注があった商品の商品名。products モデルの情報を参照している。
        tests:
          - relationships:
              to: ref('products')
              field: name
      - name: price
        description: 受注があった商品の定価。products モデルの情報を参照している。
        tests:
          - relationships:
              to: ref('products')
              field: regular_price

モデルやカラムについての説明をドキュメントに言語化しようとする時のポイントとしては、「これは一体何なのか」ということを「実際にこのデータを使うとしたらどういう情報が欲しいか」と問いかけながら書くことだと思います。このことを意識するかどうかで、orders モデルの説明文一つでも大きく異なる結果になると思います。ドキュメントも目的を持って書くことが重要です。

# これは一体何なのか、だけを気にかけた場合
models:
  - name: orders
    description: 受注データ
# 後から使うときにどういう情報が欲しいか、まで気にかけた場合
models:
  - name: orders
    description: |
      Service A から取得した生の受注データ。
      Service A は toC かつ商品単品での取引が主であるため、受注記録は基本的に1ユーザーかつ1商品単位で記録される。
      このデータは、キャンセルした分や受注後に変更があったデータも全てイミュータブルに記録している。
      [関連するモデル] 
          - order_cancel
          - coupon

まとめ

本記事では、データの理解をすることの重要性とデータについての説明をドキュメントに書くことがデータの理解に最適な方法である、についての理由を紹介しました。 データの理解をドキュメント作成を通じて行うことで、データとしてどのような状態であることが正しいかを定義できるようになります。逆説的に言うと、データについて説明できないということは、そのデータのあるべき姿がわからないということになるので、その状態でテストを書くことはオススメできません。急がば回れの精神で、テストをすぐに組む前にドキュメント作成から着手して貰えればと思います。

Data Platform チームでは、スピーディーに高品質なデータを提供できるようにするメンバーを更に募集中です。興味を持ってい頂けた方は是非以下もご覧頂ければ幸いです

Footnotes

  1. https://docs.getdbt.com/docs/build/tests

  2. https://docs.getdbt.com/docs/collaborate/documentation

  3. https://docs.getdbt.com/reference/resource-configs/persist_docs