【完全攻略】TreasureData x dbt part1

# 誰向けの記事か

* TreasureData利用企業で
    * 別DWHへの移行を考えている人
    * 基盤側の作業をデータアナリストに寄せたい人
    * dbtの導入について事例が少なくて途方に暮れている人
* dbtについてざっくり理解したい人

# はじめに
oneroots山口と申します。今回は前回記事で予告していた通りに、TreasureData x dbtの第一弾として

* そもそもdbtってなんなのか
* TreasureDataにdbtを導入するとどんなメリットがあるのか

についてまとめていきたいと思います(本記事は前置きだけで具体実装説明なしになってしまいました。。。)。

これを読み終わる頃には「dbtぜひ導入してみたい」ってなっていると第一弾の目標は達成かなと思っています。

# そもそもdbtってなんぞや
データ界隈で仕事している人ならdbtという名前は最低限聞いたことがあるんじゃないかなーと感じている程度にメジャーなプロダクトだと思うので、ご存知の方はこの章を読み飛ばしていただいて結構です。

メジャーだからこそ解説記事はそこらじゅうに転がってるので、今回は一問一答形式で押さえておいて欲しいポイントを私なりの言葉で説明したいと思います。

## Q1. dbtってなんなん？
A. data build toolの略で、(よく言われるようなETLではなく)ELTにおいてTに該当する諸々の機能をほぼSQLのみでできる(=アナリストだけでできる)ようにしています。
ELTについてもう少し説明を加えるとクラウドベースのDWHはストレージもコンピューティングリソースもスケーラブルなので、Lする前にTする必要はないことから、とりあえずDWHに突っ込んでからDWH内でTしようぜという思想(詳細はmodern data stackで検索)。

## Q2. dbtの概念はわかったけど実体はなんなの？
A. まずdbtにはdbt coreとdbt cloudという2つが存在します。
dbt coreはdbtの機能群がpythonで実装されているコード(pipでinstallできる)で、dbt cloudはdbt coreを使ったユーザーインターフェース込みの有料製品。
つまりPCなりサーバーなりにpythonをinstallしていればdbt coreをinstallして使うことができます。
dbt coreはあくまでも処理を叩けるようにする(dbt hogehogeというコマンドを使えるようにする)ものなので、別に本丸のsqlファイル群と生成先のデータセットの設定ファイル群が必要になります。

## Q3. dbtとDWHってどういう関係性なの？
A. dbt coreはDWHなどのデータ保存先に向けて「このテーブルをこのクエリに沿って更新してちょうだい」ってapiを叩いて、それを受けてDWHは処理をするという関係性。
つまりdbtは頭脳でDWHが手足という感じです。
別の例えをするとappleがデザインして鴻海がiPhoneを作るという感じです。
DWHごとにapiが変わるためdbt coreのインストールとは別に各DWHごとに接続のためのドライバーを別でインストールすることによってapi差分を吸収し様々なデータ格納先に処理させれる仕様になっています。
前述の設定ファイルの中にDWHのapiキーなどの必要な情報を格納することになります。

## Q4. dbtって具体何ができるの？
A. 前回記事に書いたので、説明を割愛させていただきます。

https://qiita.com/tkshymgch/items/48c6c024b6c971ea71b5#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB

## Q5. どうやってリネージュがわかるの？
A. sql内のテーブルを参照する箇所で`{{ ref('テーブル名') }}`や`{{ source('データセット名','テーブル名') }}`という書き方を使うことで、テーブルを作るのにどのテーブルを参照しているという依存関係がわかるようになっています。
つまりsqlに一手間加えるだけで依存関係がわかるようになるのでとてもパワフル。
また、後述するが、この参照方式を使うことで同じクエリで開発環境の実行と本番環境の実行を分けることもできるため一石二鳥。
※`{{ ref('テーブル名') }}`はdbt環境内でクエリ管理しているテーブルを参照するときに使い、`{{ source('データセット名','テーブル名') }}`はdbt環境ではクエリを管理していないテーブル(ex. dbからloadしたテーブル)を参照するときに使います。


以上がわかりやすいかわからないですが、私的にわかりやすいdbtに関しての説明になります。
この説明でわからなかったら「dbt」で検索してネットサーフィンするなり、生成AIに「dbtについて教えて」と聞いてみてください。

# TreasureDataに導入するメリット

ここまででdbtについて正しく理解したTreasureData(以降、td)ユーザーは次の疑問を持たれるかと思います。

* 普通のDWHならわかるけどtdはCDPという立ち位置でworkflow機能があるんだから必要ないんじゃないの？
* ELTのTしかやってくれないなら結局EL部分はtd workflow使うことになるんじゃないの？

これらの疑問に関して私なりの回答をすると、

* ELに関してはdbt外のworkflowなりを使わないといけないのはおっしゃる通り
* (一番変更が発生する頻度の高い)Tの処理に関してはworkflow機能よりもdbtのbuildの方が簡単かつ運用性高い
* create,insertなどの処理を共通化できて中身のクエリ以外を意識しなくて良くなる
* そもクエリをgithub管理できるだけで保守性が上がる
* 実行方法で本番環境と開発環境を作れるため開発が簡単になる
* tdから別DWHに移行がしやすくなる

になります。それぞれについて解説を加えていくと

> * ELに関してはdbt外のworkflowなりを使わないといけないのはおっしゃる通り

dbtはあくまでもELTのTに関する部分なのでそういうものです。
逆にいうと、dbtを使うことでELはエンジニア、Tはアナリストと明確に責任わけできるところがポイント高いなと思っており、エンジニアの工数を最小化しつつ、アナリストは自分の責任範囲(=自由度)を広げられるようになるためwin-winだと思っています。

> * (一番変更が発生する頻度の高い)Tの処理に関してはworkflow機能よりもdbtのbuildの方が簡単かつ運用性高い

td workflow機能の場合、自分でテーブル更新の順番を考えないといけなかったりするが、dbtはjinja記法でテーブルを指定することでそれを自動でクエリの全体最適な実行順番で処理をしてくれるため、クエリ開発するだけでよくなる。
また、dbtの場合テーブル更新後に(sqlで記載できる)テストを実行できるので、問題があった時にいち早く気がつけるというのもdbtならではの強みだったりします。

> * create,insertなどの処理を共通化できて中身のクエリ以外を意識しなくて良くなる

テーブルごとに全更新するか差分更新するかや差分更新の場合どういう記述をするかなど、共通処理に関してはクエリごとに指定しなくても、テーブルのマテリアライズ方法(ex. table,一時テーブル)やpre_hook・post_hookとして指定可能なので、(with句と)selectから始まるsqlを正しいフォルダに作るだけでテーブル更新の準備までできたことになります。
テーブル化する工数が重くないことから、必要に応じて気軽に処理を分割してテーブル化して他クエリで再利用しやすいという副次的なメリットもあります。

> * そもクエリをgithub管理できるだけで保守性が上がる

シンプルにtdってデフォでsqlやworkflow用のymlファイルをgit管理する運用になっていないから、gitで管理するという行為自体にメリットがあります。
実際にあったこととして、差分更新のテーブルなのにクエリが知らないうちに変わっていて、変更タイミングで全更新も入れてないからテーブル中に旧クエリで作られたレコードと新クエリで作られたレコードが存在すると知って、愕然としたことを覚えています。
git管理でレビュープロセスを組み込んで属人化を防ぐことはシンプルかつ絶大な効果があるので(dbt導入に対する)副次的ではありますがメリットだと思います。

> * 実行方法で本番環境と開発環境を作れるため開発が簡単になる

`{{ ref('テーブル名') }}`のように指定したテーブルは実行環境ごとにデータセットを変えることができます。
例えば本番環境(prod)で実行する場合、`xxxx.テーブル名`として読み、開発環境(dev)で実行する場合は`test_xxxx.テーブル名`として読むように設定することが可能です。
つまり同じクエリなのに実行方法を変えるだけで本番用データセットと開発用データセットを変えることができることから、本番データセットを汚さずに開発データセットでクエリ結果をレビューした上で、そのまま同じクエリを実行方法を変えるだけで本番データセットの更新へと使えます。

> * tdから別DWHに移行がしやすくなる

tdを使っているとコンピューティングリソース逼迫問題やpartition自由度が低い問題が起きるかと思われますが、その対応策として別DWHに乗り換えたいという話がしばしば出てくるかなと思います。
とはいえworkflowまでtdで管理していると移行にはだいぶ腰が重いかと思われますが、dbtを経由することで(おそらく一番量が多い)ELTのTの部分に関しては比較的簡単に移行ができます。
というのもdbt自体は前述の通りアダプターを変えて、設定情報を変えることで別DWHへの適応が可能かつ、各DWH特有の方言に関してもmacroを使うことで中身を移行先DWHで動くように読み替えることができるため、移行コストが少なくなるわけです。つまり、

(ELTのTの部分において)
* 第0フェーズ：td + td wokflow
* 第1フェーズ：td + dbt
* 第2フェーズ：別DWH + dbt

という移行ステップの第一歩としてもdbtの導入を捉えることができます。

以上がtdにdbtを導入するメリットでした。
この話はtdに寄らず、他のDWHにおいても共通する項目はあるかと思うので、読み替えて読んでいただけますと幸いです。

# tdにdbt(dbt core)を導入する際の障壁

前章に記載のメリットを享受できる可能性があるdbtなわけですが、td(主にクエリエンジンのpresto)に導入する際には他DWHにはないハードルが存在するので、part1のラストとしてそれらを列挙して行きたいと思います。

* dbt core用の環境構築やdbt記法を覚えるのが大変
* デフォのままだと`dbt run`の際に既存テーブルがあるとエラーを吐く
* 裏側でmergeが発生する処理(ex. incremental, snapshot)は使えないため機能が制限される
* クエリを叩く際にメモリ上限が存在するため、コンピューティングリソースは完全にスケーラブルではない
* sourceに対してdescriptionが記載できない

これらのハードルをどうやって乗り越えたかについては次回以降実装ベースで説明していければなと思っております。
また

> * dbt core用の環境構築やdbt記法を覚えるのが大変

に関しては前の記事に詳細をまとめておりますのでこちらを見ていただけますと幸いです。

https://qiita.com/tkshymgch/items/48c6c024b6c971ea71b5

# おわりに

今回は具体実装に入る前の導入としてdbtに関しての基本とtdに導入するメリット、tdへの導入時の障壁までを説明させていただきました。
本記事はキリがいいのでここまでとさせていただき、次回以降の記事にて具体どういう実装をしていくかについて説明していきます。

ちなみに、私はdbtに関して下記のオンラインコースで理解しました。

https://learn.getdbt.com/catalog

dbt cloudベースの説明になっているかつ時間はかかりますがdbtに関して体系的に知識を得られるので投資対効果は悪くないなと思っています(英語かつ時々字幕がおかしくなった気がしますが)。

最後に蛇足ではありますが、2024-06の執筆現在tdがtrino対応する雰囲気を感じているので、やったことが無駄にならないといいなーと思っていたりします。

https://docs.treasuredata.com/articles/#!pd/april-2024-release-note/a/h2_266358463