はじめに
はじめまして。データ基盤部に所属している23新卒データエンジニアの笠西と申します。 データ基盤部とは、全社を横断してデータ分析基盤や機械学習パイプラインの開発・運用を行っている組織です。 私が所属するグループではゲームのデータ分析基盤の開発・運用を行っています。
本稿では、データ変換処理(ELTのT)を効率的に行うためのツールである dbt とそのパッケージである elementary を活用して、ソースデータの品質テストを簡単に実現した事例についてご紹介します。
dbtとは
dbtとは、データ変換処理を担うツールです。 データ変換処理とは、データソース(例えばゲームの場合はゲームサーバやスマホなどのクライアント)から取ってきた生ログを、アナリストなどのデータ利用者が分析しやすい形に整形する処理です。 dbtを使うと、このような変換処理をSQLで書くだけで、テーブルやビューなどの作成を一挙に行ってくれます(このようなSQLをdbtではモデルと呼びます)。
ここまで書くと、通常のデータウェアハウス製品( BigQuery など)でクエリを実行するのと同じじゃないかと思われる方もいらっしゃると思うのですが、dbtには下記のような優れた点があります。
- モデル同士の依存関係を考慮して適切な順番でクエリを実行できる
- 例えば、モデルBはモデルAからfromでデータを取ってきて、モデルCはモデルBからfromでデータを取ってきている場合、モデルA -> モデルB -> モデルCの順番でクエリが実行される
- Jinjaテンプレート構文が使える
- モデル中にfor文やif文を書ける
- よく使われる処理をマクロ(関数)にして切り出して、他のモデルで流用することができる
dbtにはこのようにデータ変換処理を実装する上で優れた機能が多数あり、開発者体験の大幅な向上に寄与しています。 我々のグループでは多くの案件でdbtを活用しており、既にデータ分析基盤の実装になくてはならない存在になっています。
dbt testとは
dbtの機能の中に、dbt testと呼ばれる、ソースデータやdbtで実装した変換処理で生成されたデータに対してテストを行える機能があります。
データ分析基盤の開発においては、高いデータ品質を担保することが求められます。
品質の低いデータを提供してしまうと、データアナリストの業務に支障をきたしたり、最悪の場合誤った分析結果をもたらしてビジネス上の悪影響をおよぼす可能性もあります。
また、Garbage in, garbage out
という言葉がある通り、そもそも分析基盤に入ってくるデータの品質に問題があった場合、分析基盤内の処理に問題がなくても提供データの品質は低くなってしまいます。
そのため、ソースデータと変換後のデータをともにテストできるdbt testは重要な機能となっています。
dbt testの機能は、大きくsingular testとgeneric testの2つに分けられます。
singular testは、テスト対象のデータから存在してほしくない行をSELECTするSQLを書くことで、そのSQLの件数がちゃんと0件になるかどうかを検証するテストです。
例えば、purchase_history
という名前のモデルが生成するデータに対して、idカラムにnullが含まれないでほしいという要件があったとします。
この場合、以下のようなsingular testのSQLを書くと、idカラムにnullが含まれなければSELECT文で1件も抽出されないためテスト成功、逆にnullが含まれていればテストが落ちる、というテストを実現できます。
select id
from
{{ ref('purchase_history') }}
where
id is null
ただ、このようなsingular testを各データに対して書くのは、特に大規模なパイプラインだと非常に大変です。 より簡単にテストを実装する方法として、generic testという機能があります。
generic testでは、yamlファイルを書くだけでテストを実装できます。 例えば、以下のようなyamlを書いたとします。
models:
- name: purchase_history # モデル名
columns:
- name: id # カラム名
tests:
- not_null
このyamlファイルをモデルのSQLと同じ階層に置くだけで、上記のsingular testの例と同じように、idカラムにnullが含まれなければOKというテストが実現できます。
generic testはデフォルトで4種類が用意されており、例えば下記のyamlではそれぞれ以下の項目をテストしています。
not_null
: purchase_idカラムにnullが含まれていないかunique
: purchase_idカラムの値が全てuniqueかaccepted_values
: payment_methodカラムに’credit_card', ‘point’以外の値が入っていないかrelationships
: product_idカラムに、product_masterモデル(テーブル)のidカラムに存在しない値が入っていないか
models:
- name: purchase_history # モデル名
columns:
- name: purchase_id # カラム名
tests:
- not_null
- unique
- name: payment_method # カラム名
tests:
- accepted_values:
values: ['credit_card', 'point']
- name: product_id # カラム名
tests:
- relationships:
to: ref('product_master')
field: id
generic testは、デフォルトで用意されている上記4種類以外にも独自に実装することができます。 本項の後半で、我々が実装したgeneric testについても少しご紹介します。
elementaryとは
elementaryとは、dbt向けのデータ監視・モニタリングツールです。 先ほど説明したdbt testを強力に補完するツールであり、例えばdbt testの結果を一覧できるダッシュボードのhtmlファイルを生成したり、それをさらにSlack等に送信できる機能があります。 また、デフォルトのdbt testにはない異常値検出のテストも提供しています。 例えばある日のデータのレコード数が以前のレコード数と比較して異常に多かった/少なかった時や、値の平均値が以上に大きかった/小さかった時などにアラートを出すことができます。 上記で挙げている機能はelementaryの機能の一例であり、elementaryの他の機能について詳しく知りたい方は 公式ドキュメント を参照してください。
ここではelementaryの豊富な機能のうち、ダッシュボードについて取り上げます。 ダッシュボードの例を下図に示します。
全テストのうち何個が通って何個が落ちたのか(Test results breakdown)、全テーブルのうち何個のテーブルがテスト対象になっているか(Monitored tables)などを一覧することができます。
また別の画面では、各テスト結果の詳細を見ることができます。
例えばvolume_anomalies
テストの詳細を見ると、テスト実行日だけレコード数が大幅に減少しているため、異常値検出のテストが落ちていることが分かります。
このように、生データを見なくても何故テストが落ちたのかを一目で確認することができます。 elementaryを使うことで、このようなダッシュボードを作成すること、さらにそれをSlackへ送信することがコマンド一発でできるようになります。
dbt test & elementaryを利用したデータ品質テスト
上記のように非常に強力なツールであるdbt testとelementaryを活用して、ゲームリリース前のソースデータ品質チェックを行った事例を紹介します。
ゲームの分析基盤構築においては、まず我々データエンジニアとアナリストで分析要件をもとに出力してほしいログの仕様書を設計し、サーバ・クライアント側のエンジニアにログ出力処理を実装してもらいます。 そして、ログを実装してもらった後、出力されたログが仕様書と合致しているかどうかを検証します。 もし出力されたログが想定仕様と異なっているのに気づかず放置してしまった場合、仮に後段のパイプラインの処理に問題がなくても誤ったデータが利用者に提供されてしまいます。 このため出力されたログが問題ないかを検証する工程は必須であり、このようなソースデータ品質チェックを、我々のグループでは「データQA」と呼んでいます。
このデータQAにdbt testとelementaryを活用した事例を、具体的な手順をもとにご紹介します。 まず、ログ項目/カラムごとにどのようなテストが必要かを下図のように洗い出します。
図の行が各カラム、列が各generic test項目に対応しています。 ログ項目/カラムの性質を考慮してこの図を埋めていけば、あとはその内容をyamlに変換するだけでテストの実装が完了します。 dbtがなかった頃は、各ログ項目/カラムに対して一つ一つテストクエリを書かないといけなかったと考えると、dbtのおかげで圧倒的にテストの実装が楽になったと言えます。
generic testで担保できる範囲を広げるために、デフォルトの4種類のgeneric testだけではなく独自のgeneric testも実装してデータQAに使用しています。
例えば、above_specific_value
というgeneric testは、「全レコードに指定した数値以上の値が入っているかどうか」をテストします。
例えば0を指定すると、負の数が1レコードでも入っていたらテストが落ちます。
また、one_hot
は「複数カラムのうち、1カラムだけ1で他はすべて0が入っているか」をテストします。
もちろん、generic testだけでは実装できない複雑なテスト要件も存在するのですが、この場合は「シナリオテスト」を行っています。 シナリオテストは、ログが正しく出力されているか確認したい操作をまとめた手順書を作り、その手順書通りにゲームを操作してログが期待通りの順番、内容で出力されているかを検証するテストです。 シナリオテストを行うことで、サンプル数は少ないものの、ログが仕様通りに出力されているかをより正確に検証できます。 このシナリオテストはsingular testとして記述することができるので、dbt testでgeneric testと同時に実行することができます。 このように、generic testやシナリオテストを組み合わせて行うことで、ソースデータの品質が問題ないことを担保しています。
そして、スムーズにデータQAの結果を確認してチーム内で共有するために、elementaryを活用しています。 データQAは定期実行する必要がないため、基本的にデータQA専用のdbtプロジェクトを立てて各メンバーのローカル環境で行っています(もちろん運用後も継続的にテストしたい項目については、メインのdbtプロジェクトにテストコードを移植して定期実行させます)。 先述の通りelementaryを使うことで、コマンド一発でローカル環境で行ったdbt testの結果をレポートにまとめ、Slackへ送信できます。 そして作成されたレポートを他のチームメンバーが見ることで、どこでテストが落ちたのか、何故落ちたのかを一目で把握することができます。 これによって、チームメンバーへのテスト結果の共有やその後の役割分担をスムーズに進めることができます。
もちろん、dbt testやelementaryは、リリース前のデータQAだけではなく運用開始後の定期的なデータ品質チェックにも活用できます。 我々のグループでも、運用中のゲームタイトルでは毎日dbt runの後にdbt testを実行し、その結果をelementaryレポートとしてSlackに投稿するワークフローを運用しています。 dbt testやelementaryは、開発と運用の両方のフェーズで活用できるツールです。
おわりに
本稿では、dbt testとelementaryを活用してソースデータの品質を担保する取り組みについてご紹介しました。 本事例が、データ品質の向上に向けて取り組まれているデータエンジニアの皆様の参考になりましたら幸いです。
最後まで読んでいただき、ありがとうございます!
この記事をシェアしていただける方はこちらからお願いします。