【dbt】分析のプロジェクトでのテーブル管理を考える


## はじめに
この記事は[BrainPad Advent Calender](https://qiita.com/advent-calendar/2022/brainpad) 19日目の記事です。
本日は[dbt (data build tool)](https://www.getdbt.com/)について書かせていただこうと思います。

dbtは、ETLのtransformのワークフローをいい感じに管理してくれるツールです。
日本語でも既にたくさんの分かりやすい資料が公開されていますので、一般的な説明はそちらにお任せして割愛します。([dbtとは?](https://zenn.dev/dbt_tokyo/books/537de43829f3a0/viewer/what_dbt)など)

主にデータエンジニアの方々の中で広がっている印象を受けるdbtですが、本記事はデータサイエンティストも分析の現場で導入する価値があるのではないかという話をさせていただきます。

:::note info
dbtにはGUIベースのdbt cloudとCLIベースのdbt coreの2種類がありますが、基本的にdbt coreの前提で話をします。

:::



## 想定しているdbtの使い方
本記事では、データサイエンティストがデータの内容を確認しつつ課題に対してどのようなアプローチができるかを検討する段階や、その検証を行うような分析のフェーズでdbtを利用する状況をぼんやりと念頭に置いています。

したがって、**データに関する情報が曖昧**であったり、そもそも**何をしたいのかという部分が不明確な場合**も含まれており、基本的にはかっちりと仕様を決めてテーブルを管理するためというよりは、dbtの**最低限のルールだけ守りつつ、かなりライトに使うこと**を想定しています。

具体的には、SQL(やpython)でmodelを定義してテーブルを作成しつつ、その周辺の[documentation](https://docs.getdbt.com/docs/collaborate/documentation)や[tests](https://docs.getdbt.com/docs/build/tests)の利用を考えています。
逆にいえば、複雑な[macro](https://docs.getdbt.com/docs/build/jinja-macros)を組んで、依存を作りすぎない方が良いと思っています。プロジェクトが進んでシステム化する際にdbtを使わない可能性もあるので、なるべく疎結合にしておきたいですね。

## 分析の現場におけるdbtの恩恵

### lineageがから自動作成できる
lineageは[documentation](https://docs.getdbt.com/docs/collaborate/documentation)の機能です。
画像のように、どのテーブルがどのテーブルの作成に必要なのかを示す関係図を自動生成してくれます。

![lineage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410107/b40f063e-3c16-56f1-f475-3f82855d623c.png)
*[公式documentより](https://docs.getdbt.com/docs/collaborate/documentation)*


データリネージの有用さは言わずもがなかもしれませんが、
- あるテーブルを修正したいがどの範囲のテーブルや集計結果に影響があるのか
- そもそもテーブルを修正するためにどのテーブルから見直せばいいのか
- 作ろうと思っているテーブルに近いものがすでにあるのか

など、様々なことが図から読み取れます。もちろん担当メンバーに確認すれば分かることですが、そのコミュニケーションがスキップできることが大切です。担当者がすでにプロジェクトにいない可能性もあります。属人化を駆逐していきましょう。

SQLを書けば自動的に生成されるという部分も重要です。
カラムの意味合いすら曖昧で探索的な進め方をしている状況ですと、どうしても一時的なテーブル・やむを得ず作った中間テーブルなどが乱立してしまいます。
そうなると、プロジェクト内で使われるテーブルの更新の頻度が高まっていいき、**人手で管理している関連図は必ずどこかで更新されなくなります**。自動で作成できるドキュメントは自動で作成しましょう。

### テーブル定義書が自動生成される
同じく[documentation](https://docs.getdbt.com/docs/collaborate/documentation)の機能です。
テーブル定義書がsqlと同じリポジトリ上で管理される点がまずうれしいですね。
![dbt-docs-screenshot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410107/5d65f44c-e128-4382-f2cd-ab265ec6fe43.png)
*[公式documentより](https://docs.getdbt.com/docs/collaborate/documentation)*

こちらは`description`などをある程度人手でyamlを書かなければなりませんが、必要な作業でしょう。
少なくとも、何もせずカラム名とその型は表示してくれるので最低限のところまで自動で生成してくれます。

また、サードパーティーにはなりますが、yamlファイルへのカラム名などをある程度自動生成するpackageがあります。

- [dbt-osmosis](https://github.com/z3z1ma/dbt-osmosis)
- [dbt-codegen](https://github.com/dbt-labs/dbt-codegen)


### 単純なテストが簡単にできる
[tests](https://docs.getdbt.com/docs/build/tests)の機能です。
例えば、トランザクションデータなどを追加で受領するということは受託分析ではよくあります。
もちろん最初に「おかしなデータがないか」を確認しますが、**複雑なドメイン知識などが不要な内容や、ルーティン化しているチェックは自動で行いたい**ですね。dbtはデフォルトで4つのテストを備えており、yamlに記述することで簡単に実行できます。

```
# https://docs.getdbt.com/docs/build/tests より
version: 2

models:
  - name: orders
    columns:
      - name: order_id
        tests:
          - unique
          - not_null
      - name: status
        tests:
          - accepted_values:
              values: ['placed', 'shipped', 'completed', 'returned']
      - name: customer_id
        tests:
          - relationships:
              to: ref('customers')
              field: id
```

| test              | 内容                                                 |
| ----------------- | ---------------------------------------------------- |
| `unique`          | カラムの値がユニークである                           |
| `not_null`        | カラムの値にnullが含まれない                         |
| `accepted_values` | カラムの値に`values`で指定した値以外を持たない       |
| `relationships`   | `to` で指定したテーブルの `filed` カラムと同じである |

またsqlを追加で書く必要はありますが、独自のtestを作成することもでき、受領のごとのチェックのためだけでなく新しいメンバーにどのような観点でデータを見れば良いかを簡単に共有できます。

### テーブル関係の複雑化を抑制
ここだけ若干抽象的な内容になりますが、dbtの枠組みでテーブルを作成することになるので、ある程度統一された構造を保ちつつ分析のテーブルが作成されるメリットも考えられます。

分かりやすいものですと、例えば**DAGの構造を満たす**テーブル関係になります。そもそも循環参照するような関係を作るなという話ではありますが、アドホックにデータを扱っていると何が起こるか分からないので、システム的に防げるのはうれしいですね。ちなみにdbtはコンパイルの段階で循環参照を検知してエラーを出してくれます。ありがたや。

<!-- またプロジェクトが開発フェーズになる際に、移行がしやすくなるかもしれません。
分析環境にもよりますが、pandasなどでデータの加工をするか、sqlでデータを加工するかがタイミングや人によってバラバラになってしまい、結局全てを開発開始時に作り直すということも起こり得ます。 -->
また、dbtというベースの環境があることで、**テーブルのレイヤをどうするか**や、**どの処理を何に任せるか(pandasやsqlなど)** という基準が設けやすくなるかもしれません。半分願望が入っているかもしれませんが、少なくとも絶望的に散らかってしまうことはないでしょう。ある程度のデータ操作が統一されることで、分析からシステム化のフェーズになった時に、全てを書き直すということを防げるかもしれませんね。

## 分析の現場へ導入する上での障壁

### ドキュメントを静的に取り出しにくい
メリットとしてご紹介したlineageとテーブル定義書のdocumentに関して、データサイエンティストだけが使う分には問題ではないのですが、プロジェクト外のリポジトリへのアクセス権がないメンバやcli操作に慣れていない方に共有するとなると若干取り回しが面倒です。
表示されているdocsが静的なページとして保存されていない関係で、現時点では簡単にエクスポートができないようです。
これは[issue](https://github.com/dbt-labs/dbt-docs/issues/53)としてもあがっていますね。

### データソースとの連携が必要
データレイクやデータウェアハウス等にデータが登録されている前提で動作するツールのため、そういった基盤がない場合は構築するところからになるため、ハードルがあがるかもしれません。
軽量なデータをローカルだけで扱いたい場合は、ローカルでDBを立ち上げて[seeds](https://docs.getdbt.com/docs/build/seeds)の機能を使う方法などが考えられるかもしれません。例えばDuckDBを使う方法は[こちらの記事](https://zenn.dev/takimo/articles/bb11eab78232f4)で紹介されています。

### 機械学習パイプラインとの組み合わせが悩ましい
これは私自身の知識不足なのかもしれませんが、dbtと機械学習パイプラインを組み合わせたときに、処理の切り分けをどのように設計にするかが難しいように感じられます。

例えばdbt上で作成した特徴量のテーブルを読み込み、予測を行い、結果を改めてテーブルに格納するという状況を考えると、outputのテーブルは若干dbtの枠組みからは外れる、もしくは新しいsourceテーブルとしての管理になってしまうため、テーブルの関係が複雑になってしまいます。
予測を行ってoutputを出したらおしまいであればいいのですが、往々にしてさらにそこからテーブルの変換を行っていくので、単純なテーブルの関係で収まらない可能性が高いでしょう。


## 結び
簡単ではありますが、社内の方々と議論していた内容をもとに、データサイエンティストがdbtを使う場合どのようなメリット/デメリットが考えられるかを紹介させていただきました。
ドキュメントの管理は重要ではあるものの可能な限り楽をしたいところですので、こういったツールを使いつつうまく自動化して、より分析などの本質的な作業に注力できるよう工夫していきたいです。

という月並みな感想で、本記事は締めくくらせていただきます（笑）

ここまで読んでいただきありがとうございました。残りのアドベントカレンダーもぜひお楽しみください。