はじめに
DRE&MLOps チームの hyamamoto です。 最近は涼しくなってきて、秋の気配が感じられるようになってきましたね。 秋は一番好きな季節なので嬉しいです。
さて、今回は dbt について少し変わった切り口で紹介します。 今回の紹介において主眼に置きたいことは以下の内容です。
- dbt はそもそもツールとして何を行なうか
- dbt は技術的にどのようにしてその機能を実現しているか
- その機能の結果 dbt はアプリケーションとしてどのような利点があるか
ここでは dbt の各機能には触れず、あくまでもブラックボックス化した dbt の基礎を理解することで、 dbt の概念理解の土台を作ったり、利用時の問題について技術的な目線からアプローチできるようになったりすることを目的としています。 対象読者としては、ソフトウェアエンジニアの方で実装目線から dbt を理解したい方や、データアナリストの中で dbt をより深く知り、日々の業務に活かしたい方を想定しています。
宣伝にはなりますが、過去、Terraform についても同じようなニュアンスの記事も書いたので、興味のある方はぜひ読んでみてください。 tech.gunosy.io
dbt はそもそもツールとして何を行なうか
dbt を理解する上でまず把握しておくべき概念として ELT (Extract, Load, Transform) があります。 なぜなら、dbt は ELT における Transform 部分を担うツールだからです。
ここでは ELT についての全体像の説明は記事の長さの都合から割愛しますが、下記の資料は ETL (Extract, Transform, Load) と ELT の違いについてわかりやすくまとめられているので、興味のある方はぜひ読んでみてください。
ELT における Transform について
ELT におけるデータ変換 (Transform) では Athena や BigQuery などデータウェアハウスの計算機能を利用して、データ変換を行ないます。 この変換の技術は BI ツールにおいて SQL を使ってデータを可視化していることと同様の技術です。 というのも、可視化したデータというものは SQL によって変換されて出力されたものだからです。 この技術を可視化だけに使うのではなく出力されたデータを保存することで、データ変換のロジックにも応用することが可能になり、ELT におけるデータ変換の技術として利用されるようになりました。
このようにデータウェアハウスの計算機能に依存して、データ変換をすることによるメリットは次のようなものが考えられます
- データウェアハウスの計算資源をそのまま利用できるため、特殊な分散処理基盤を用意する必要がない
- 特に Athena や BigQuery はサーバレスなツールであるため、計算資源の管理が不要ですぐに大規模なデータの処理が可能になる
- SQL による汎用的な宣言型言語でデータ変換を表現することができる
- 汎用性の高い言語であるため、幅広いユーザーが利用できる
- データ基盤ごとに多少の違いはあるものの、大枠として SQL の記法や処理のための流れは共通している
- 汎用性の高い言語であるため、幅広いユーザーが利用できる
まとめると ELT における Transform というものは、かつて分散処理基盤や Spark のような専門性の高い技術を用いて行われていたデータ変換を、 クラウドの提供する計算資源に直接依存して SQL という汎用言語によるデータ変換に置き換えることを可能にしたと言えます*1。
dbt が担っている機能
以上を踏まえて dbt が担っている機能について大きく 3 つが挙げられます。
- データ変換をする上での SQL の生成
- 各種データ基盤 (e.g. Athena) に向けたデータ変換の手続きの抽象化
- 生成された SQL に基づくデータ変換の手続きの実行
私個人の見解ですが、dbt によるメタデータ管理や test 機能などは上記の内容の延長上にあるだけで、 dbt の技術としての基盤ではないと考えています。
それでは、dbt がどのようにして上記の機能を実現しているかについて詳しく見ていきます。
dbt は技術的にどのようにして Transform を実現しているか
ここでは dbt がどのようにして Transform を実現しているかについて、より技術的な内容に基づいて深掘っていきます。
Jinja テンプレートによる SQL の生成
まず、dbt が提供する重要な機能として Jinja テンプレートによる SQL の生成があります。 dbt によるデータ変換を記述したことがある方ならイメージが湧きやすいと思うのですが、ユーザーとして dbt を用いる際に記述する SQL は以下のようなシンプルなものです。
SELECT "date" , count(1) as n FROM {{ ref("foo") }} GROUP BY 1
ここでは foo
というモデル(テーブル)に依存して日付ごとのレコード数を算出する変換クエリになっています。
見て分かる通り、このクエリはデータ変換のためのクエリというよりも、あくまで普段分析で利用しているクエリと全く同じものになっています。
その理由は dbt がデータ変換処理の実行時において、上記のクエリを別のテンプレートに埋め込んでいるためです。
データ基盤ごとの細やかな違いはあるものの、おおよそ下記のようなクエリが生成され、dbt によって実行されています。
[START]
から [END]
にかけての部分はユーザーが記述したものであり、これを CREATE TABLE AS
文に埋め込むことでデータ変換の処理として実行することが可能になっています。
CREATE TABLE AS -- [START] ユーザーの記述したクエリ SELECT "date" , count(1) as n FROM {{ ref("foo") }} GROUP BY 1 -- [END] ユーザーの記述したクエリ
CREATE TABLE AS
文は略して CTAS 文と言われ、SELECT した結果をそのままテーブルとして保存することを可能にする SQL です。
「SELECT した結果をそのままテーブルとして保存」はデータ変換そのものをと言えるため、このような SQL を生成し、分析基盤に実行を依頼することでデータ変換を実現できます。
このようなクエリを生成する Jinja テンプレートは dbt-core における下記を参照すると実装を確認することができます。
各種データ基盤に向けたデータ変換の手続きの抽象化
dbt は特定のデータ基盤に向けて作られたツールではありません。 一方で、先程から述べている通り各種データ基盤ごとに SQL の文法は異なるため、dbt は各種データ基盤に対応した SQL を生成する必要があります。 この問題を解決するために dbt はデータ変換の処理のインターフェースを定義し、それを各種データ基盤向けの package に実装させるようにしています*2。 言い換えるとあくまでも dbt が提供するのはデータ変換の枠組み(e.g. materialize の方式や test の設計など)になっているとも言えます。
それでは各種データ基盤向けの package がどのような実装を持っているかについて具体例で見ていきます。 例えば、Athena の場合において CTAS を実行しようと思った際は SQL の中にその出力先の S3 の情報なども含む必要があります。 これは dbt 本体が考慮すべきものではありません。 Athena 特有の S3 を考慮した CTAS の実装については dbt-athena によって実装されており、これに対応する部分は下記の部分になります。
CTAS の際に紹介した実装と上記の実装を見比べると、dbt-core における create_table_as
という macro を dbt-athena 側で上書きしていることが見て取れます。
以上のことから dbt は各種データ基盤に対応した SQL を生成するために、データ変換の処理のインターフェースを定義し、それを各種データ基盤向けの package に実装させることで、幅広いプラットフォームへの対応を実現していることが分かります。
生成された SQL に基づくデータ変換の手続きの実行
最後に紹介したい dbt の役割としては生成された SQL の実行です。
これは言葉の通りシンプルではあるのですが、強調したい点としてデータ変換に伴うクエリは CTAS 文だけではなく、その処理系として複数のクエリを実行する必要があるということが挙げられます。
例えば、incremental なモデル(更新のあった差分データを取り込んでいるモデル)の場合において、カラム名の追加削除があった場合、そのスキーマの変更を反映するための ALTER 文を実行してから、データの更新を行なう必要があります。
このように、一言でデータ変換と言っても裏側では複数の処理が行われる場面もあるため、dbt はそれら一連の流れを手続きとして実行してくれます。
もちろん、ユーザー目線ではあくまでデータ変換用の SELECT 文を記述するだけではあるのですが、裏側でそのような複数の処理が行われていることを認識することは重要です。
具体的なクエリについては dbt の CLI 実行時に --debug
オプションをつけることで確認することができます。
さいごに
以上のことを理解すると dbt の裏の実装が透けて見えてきて理解が深まると思います。
例えば、Jinja テンプレートの役割を理解すると dbt の test は yaml からその整合性チェックに必要なパラメーターを受け取り、そのパラメーターを元に SQL を生成して実行しているのだと理解できます。 また、リネージというデータ変換の依存関係を可視化する機能についても、Jinja によってデータ取得元の情報を構造化できているため、それを利用して可視化を行なっているということもわかります。
実装から機能を把握することができるようになると、各オプションの意味を実装レベルで把握することも可能です。 その結果、問題が発生したときの切り分けとして自身の設定なのか、それとも dbt および接続 package 側の問題なのかを切り分けることができるようになります*3。
今回の内容が dbt の概念理解の土台になれば幸いです。