dbtのディレクトリ構成について

2024-06-06

データプラットフォームチームの kobori です。Belong に入社して 4 ヶ月が経ちました。 SQL を書き続ける毎日を過ごしています。

弊社では dbt を使い BigQuery 上にデータ基盤を構築しています。 dbt は SQL を書くだけで、オーケストレーションの意識もほとんど必要なくデータパイプラインを構築でき、リネージの可視化まで簡単にできるので非常に便利に使っているのですが、dbt の Best Practice で推奨されている 3 層構成がどうも分かりづらく頭を抱えていました。

dbt で推奨している構成について

dbt では下記の 3 層で dbt プロジェクトを構成することを推奨しています。

  • staging
    • dbt のプロジェクトにおいて、ルートとなるモデルが配置されるレイヤです。 カラム名の統一や Type Cast などの標準化を行います。
  • intermediate
    • mart モデルをシンプルに保つために、複雑なロジックを切り出すためのレイヤです。 mart モデルの CTEs が大きくなりすぎる場合などは、intermediate に切り出します。 そのため、ビジネスロジックが集中するレイヤと考えることもできるかもしれません。 intermediate モデルをうまく使うことで、DRY なモデリングを行うことができます。
  • mart
    • ユーザがアクセスするモデルを配置するレイヤです。 売上や集客数のような、ビジネス上必要な情報を保持するモデル (Metrics) が配置されます。

これらのレイヤ上にモデルを配置していきます。 モデルはデータソースを起点として、staging を経由し mart モデルが構築されます。 このような、どのデータがどこで生成されどのように構築されるかのような、データの辿る系譜のことを lineage と呼びます。 lineage は DAG (有向非巡回グラフ) を形成します。

dbt DAG

何が難しかったか

上記の 3 層のうちの、特に intermediate 層の在り方に疑問を感じていました。 intermediate 層がレイヤの関係の逆転を引き起こし、DAG を複雑にしてしまうように見えていたからです。

例えば下記のようなリネージが形成されるケースです。

5 step models

矢印は参照の向きを表しています。右側がソース側、左側がプレゼンテーション側となります。 mart モデルからさらに intermediate モデルを経由し mart モデルを構成するようなリネージを形成しています。

これが私にはこのように見えていました。

dbt layering

レイヤ間の参照関係に注目すると、モデル間を結ぶ矢印が intermediate 層から mart 層に対して伸びています。 個人的に、モデルだけではなくレイヤについても DAG が成立する方が美しくわかりやすい構成のような気がしているため、このモデリングには違和感がありました。

そこで、このレイヤ間の相互参照を解消するため下記のようなモデリングを検討しました。

spaghetti lineage

intermediate 層から mart 層への参照をなくすために intermediate モデルを 1 つ追加しています。 intermediate モデルから mart モデルを参照したい場合は代わりにこのモデルを参照し、mart モデルもこのモデルを参照するような構成になります。 しかし、この構成では、mart モデルを参照したい intermediate モデルが生じるたびに mart モデルを intermediate モデルで置き換えることになり、必要以上にモデルが増え、メンテナンス性にも欠ける構成になってしまいます。

どうしたか

mart 層と intermediate 層の間での相互参照を一度は受け入れ、Best Practice の構成に従おうとしました。 dbt の推奨している 3 層は、あくまで dbt プロジェクト上の構成の話であり、DWH 上に出現するレイヤとは別物なのだと考えたためです。 intermediate モデルは膨らみすぎる CTEs を吸収するために存在するため mart モデルとともに出現します。そのため DWH 上ではレイヤを構成しません。 本来は staging → intermediate → mart のように綺麗に 3 層を成せばよいのですが、marts は膨らみやすいため、そのような綺麗な分割は不可能です。 そのため dbt プロジェクトとしての分かりやすさのために、ある種妥協のようなことをして 3 層構成を推奨しているのだろうと思うことにしました。

一度は受け入れたのですが、その後社内で同様の議論が起こり、やはりエンジニアにはこの構成はわかりづらすぎると感じたため、結局レイヤ構成を再定義することになりました。 詳細はまた別の記事で紹介したいと考えているのですが、その構成では intermediate を下記のように捉えることにしました。

belong layering

ディレクトリの構成は次のようなイメージです。

models/
├── staging/
└── mart/
    └── intermediate/

intermediate をレイヤとして捉えるのをやめ、膨らむ CTEs を吸収するための ephemeral モデルの集合と捉えることにしました。 そのため、staging や mart と並列には配置せず、marts 内のサブディレクトリとして配置しています。

実際にはこれよりもレイヤは多くサブレイヤも区切っているためもう少し複雑な構成にはなります。

最後に

つまり何が言いたいのかというと、dbt の Best Practice って本当に Best なんでしょうか? 私は少し分かりづらく感じたのですが、皆さんはすぐに受け入れられましたか?

Best は様々変わるとは思うのですが、私が素直すぎるあまり dbt の Best Practice に振り回されたという話でした。

そんな私の働いている Belong の Entrance Book を是非覗いてみてください!