💨

dbtでプロジェクト越えができるdbt-loom

に公開

tl:dr

  • 複数のdbtプロジェクトを跨いでモデルを参照できるよ
    • モデルを参照 => refやリネージュ
  • dbt-coreでも使えるよ
  • dbt docsやdbt-power-userのlineageにも反映されるよ

dbt-loomとは

公式サイト曰く

dbt-loom is a dbt Core plugin that weaves together multi-project deployments. dbt-loom works by fetching public model definitions from your dbt artifacts, and injecting those models into your dbt project.

で、dbt projectを超えて、あるプロジェクトのdbt modelから別のプロジェクトのmodelを参照するツール(dbt plugin)です。

用途やチームによって、dbt projectを分けるような場合に使えます。dbt-loom使わなくても

  • モデルに対応するテーブル・ビューを参照
  • sourceにupstreamのモデルのテーブル・ビューを追加

のような方法でも参照できますが、

  • dbt-loomを使うことでプロジェクト跨いだ依存関係が明示的
  • sourceの設定が不要に

のようなメリットがあります。また、upstreamからdownstreamのモデルの依存を確認することにも使えます(後述)。

使ってみる

  1. 参照先(upstream)のモデルを準備
  2. 参照したい先(upstream)のdbt projectのmanifest.json/テーブルを用意
  3. 参照する元の(downstream)でdbt-loomをインストール
  4. 参照する元の(downstream)で設定ファイルに記載
  5. 参照する元の(downstream)でupstreamのモデルを参照
  6. 参照する元の(downstream)でcompile/run

するだけです。

dbt-loomのリポジトリにサンプルプロジェクトがあるので、それを使ってみましょう。
このプロジェクトは(たぶん)dbtでよくサンプルとして使われるJaffle Shopを応用したもので、

  • revenueプロジェクト(upstream)
  • revenueのモデルを参照している(downstream)

の二つのプロジェクトがあります。

参照先(upstream)のモデルを準備

dbt-loomを使わない時と同様にモデルを用意するだけですが、一点注意点としてaccess属性もしくはrestrict-accessを設定して、そのプロジェクト以外から参照できるようにしておく必要があります。

参照したい先(upstream)のdbt projectのmanifest.json/テーブルを用意

revnueプロジェクトでdbt runするだけです。こちらはdbt-loomはなくても大丈夫です。

> pwd
/home/notrogue/project/dbt-loom/test_projects/revenue
# このrevenueプロジェクトではseedsが必要なのでそちらも実行
> dbt seed
> dbt run

一つ上のディレクトリにDuckDBにテーブルが作成されます。

duckdb ../database.db -c '.tables'
base_exposure_relationships
base_metric_relationships
base_node_columns
base_node_relationships
base_source_columns
dbt_project_evaluator_exceptions
fct_chained_views_dependencies
fct_direct_join_to_source
fct_documentation_coverage
fct_duplicate_sources
fct_exposure_parents_materializations
fct_exposures_dependent_on_private_models
fct_hard_coded_references
fct_marts_or_intermediate_dependent_on_source
fct_missing_primary_key_tests
fct_model_directories
fct_model_fanout
fct_model_naming_conventions
fct_multiple_sources_joined
fct_public_models_without_contract
fct_rejoining_of_upstream_concepts
fct_root_models
fct_source_directories
fct_source_fanout
fct_sources_without_freshness
fct_staging_dependent_on_marts_or_intermediate
fct_staging_dependent_on_staging
fct_test_coverage
fct_test_directories
fct_too_many_joins
fct_undocumented_models
fct_undocumented_public_models
fct_undocumented_source_tables
fct_undocumented_sources
fct_unused_sources
int_all_dag_relationships
int_all_graph_resources
int_direct_relationships
int_model_test_summary
integers
orders_v1
orders_v2
seed_accounts
stg_columns
stg_exposure_relationships
stg_exposures
stg_locations
stg_metric_relationships
stg_metrics
stg_naming_convention_folders
stg_naming_convention_prefixes
stg_node_relationships
stg_nodes
stg_order_items
stg_orders
stg_products
stg_sources
stg_supplies

参照する元の(downstream)でdbt-loomをインストール

dbt-loomのインストールはpip経由で行います。test_projectではpyproject.tomlに記載されてるのでpoetry installを行います。

参照する元の(downstream)で設定ファイルに記載

dbt_loom.config.ymlに参照したい先(upstream)のmanifest.jsonを指定します。
(nameの部分は適当)

manifests:
  - name: potato
    type: file
    config:
      path: ../revenue/target/manifest.json
    excluded_packages:
      - dbt_project_evaluator

参照する元の(downstream)でupstreamのモデルを参照

refで、upstreamのプロジェクト名(dbt_loom.config.yamlではなくupstreamのdbt_project.yamlのname)・モデル名を指定するだけです。

例えば、test projectのcustomersではcustomers.sqlで、upstream(revenue)のordersモデルを参照しています。

    select * from {{ ref('revenue', 'orders') }}

依存が参照できることをdbt lsで見てみます。

> dbt ls -s '+customers'
00:35:15  Running with dbt=1.10.1
00:35:15  Initializing dbt-loom=0.9.0
00:35:15  dbt-loom: Patching ref protection methods to support dbt-loom dependencies.
00:35:15  dbt-loom: Loading manifest for `potato` from `file`
00:35:15  Registered adapter: duckdb=1.9.3
00:35:15  dbt-loom: Injecting nodes
00:35:15  [WARNING]: Model orders.v1 has passed its deprecation date of 2024-01-01T00:00:00+09:00. This model should be disabled or removed.
00:35:15  Found 13 models, 5 data tests, 1 source, 541 macros
customer_success.marts.customers
customer_success.staging.stg_customers
revenue.orders.v2
revenue.stg_accounts
revenue.stg_locations
revenue.stg_order_items
revenue.stg_orders
revenue.stg_products
revenue.stg_supplies
revenue.integers
revenue.seed_accounts
source:customer_success.ecom.raw_customers
customer_success.marts.accepted_values_customers_customer_type__new__returning
customer_success.marts.not_null_customers_customer_id
customer_success.staging.not_null_stg_customers_customer_id
customer_success.marts.unique_customers_customer_id
customer_success.staging.unique_stg_customers_customer_id

revenueのモデル(ordersやstg_accounts等)も参照できてますね。

参照する元の(downstream)でcompile/run

runするだけです。

> dbt run -s '+customers'
00:37:59  Running with dbt=1.10.1
00:37:59  Initializing dbt-loom=0.9.0
00:37:59  dbt-loom: Patching ref protection methods to support dbt-loom dependencies.
00:37:59  dbt-loom: Loading manifest for `potato` from `file`
00:37:59  Registered adapter: duckdb=1.9.3
00:37:59  dbt-loom: Injecting nodes
00:37:59  [WARNING]: Model orders.v1 has passed its deprecation date of 2024-01-01T00:00:00+09:00. This model should be disabled or removed.
00:37:59  Found 13 models, 5 data tests, 1 source, 541 macros
00:37:59
00:37:59  Concurrency: 4 threads (target='dev')
00:37:59
00:37:59  1 of 2 START sql view model main.stg_customers ................................. [RUN]
00:37:59  1 of 2 OK created sql view model main.stg_customers ............................ [OK in 0.08s]
00:37:59  2 of 2 START sql table model main.customers .................................... [RUN]
00:37:59  2 of 2 OK created sql table model main.customers ............................... [OK in 0.06s]
00:37:59
00:37:59  Finished running 1 table model, 1 view model in 0 hours 0 minutes and 0.26 seconds (0.26s).
00:37:59
00:37:59  Completed successfully
00:37:59
00:37:59  Done. PASS=2 WARN=0 ERROR=0 SKIP=0 NO-OP=0 TOTAL=2

なお、downstream(customer_success)をrunしても、upstream(revenue)のモデルはrunされません。

revenueと同じDuckDBのデータベースにテーブルが追加されています。

> duckdb ../database.db -c '.tabl' | grep customer
customers
stg_customers

中身も入ってます。count_lifetime_ordersなどがrevenue由来のカラムです。

> duckdb ../database.db -c 'select * from customers limit 1'
┌──────────────────────────────────────┬───────────────┬───────────────────────┬─────────────────────┬─────────────────────┬───────────────────────┬───────────────────┬───────────────┐
│             customer_id              │ customer_name │ count_lifetime_orders │  first_ordered_at   │   last_ordered_at   │ lifetime_spend_pretax │  lifetime_spend   │ customer_type │
│               varchar                │    varchar    │         int64         │      timestamp      │      timestamp      │        double         │      double       │    varchar    │
├──────────────────────────────────────┼───────────────┼───────────────────────┼─────────────────────┼─────────────────────┼───────────────────────┼───────────────────┼───────────────┤
│ 96c8937f-d99c-4515-acec-db24d07768f9 │ Gerald Odom   │          120          │ 2016-09-01 09:35:00 │ 2017-08-31 14:40:00 │        2858.0         │ 3029.389995574951 │ returning     │
└──────────────────────────────────────┴───────────────┴───────────────────────┴─────────────────────┴─────────────────────┴───────────────────────┴───────────────────┴───────────────┘

エコシステム

ここまででdbt-loomで、dbt compile/run時に別のプロジェクトのモデルを参照できることがわかりました。

ただ、実際にdbtを使う時は、dbt compile/runだけを使ってるわけではなく、それ以外のツールも使っていると思うので、対応ツールを紹介していきます。
(他にも対応してるツールはあると思います。)

dbt-power-user

対応しており、特に設定せずともlineageが見えます。

下図がcustomer_successプロジェクトのcustomersモデルを開いた時のlineageで、revenueモデルを参照できています。

dbt docs

dbt docsでもupstreamのモデルがlineageに表示されます。

制約

上述の通り、dbt-power-user・dbt docsのlineageに、dbt-loom経由で参照しているupstreamのモデルは表示されますが、そのモデルのdescriptionやカラム名は表示されません。

これは、dbt-loomが実装に使っているdbt plugin(nodeを注入する機能)というdbt-core機能の制限で、pluginではカラム・descriptionを注入できないためです。
(dbt-coreにPR出したのですがマージに至らず)

upstreamからdownstreamを参照する

上述の例は、downstream(customer_success)にdbt-loomを入れてupstream(revenue)を参照していました。
実はdbt-loomを逆方向に導入、upstream(revenue)にdbt-loomを入れてdownstream(customer_success)を参照することも出来ます。

# revenueのdbt_loom.config.yml
> cat dbt_loom.config.yml
manifests:
  - name: potato
    type: file
    optional: true
    config:
      path: ../customer_success/target/manifest.json
    excluded_packages:
      - dbt_project_evaluator

こうすることで、upstream(revenue)のモデルのdownstreamでの利用状況を確認することができます。

# downstreamのモデル(customers)が含まれます
> dbt ls -s 'orders_v2+'
01:16:57  Running with dbt=1.10.1
01:16:57  Initializing dbt-loom=0.9.0
01:16:57  dbt-loom: Patching ref protection methods to support dbt-loom dependencies.
01:16:57  dbt-loom: Loading manifest for `potato` from `file`
01:16:57  Registered adapter: duckdb=1.9.3
01:16:57  dbt-loom: Injecting nodes
01:16:57  [WARNING]: Model orders.v1 has passed its deprecation date of 2024-01-01T00:00:00+09:00. This model should be disabled or removed.
01:16:57  Found 59 models, 3 seeds, 51 data tests, 5 sources, 602 macros, 1 group
customer_success.customers
revenue.marts.orders.v2
revenue.marts.dbt_utils_expression_is_true_orders_v2_count_food_items_count_drink_items_count_items
revenue.marts.dbt_utils_expression_is_true_orders_v2_subtotal_food_items_subtotal_drink_items_subtotal
revenue.marts.not_null_orders_v2_order_id
revenue.marts.unique_orders_v2_order_id

upstream(revenue)のモデルはdownstream(customer_success)のモデルを参照しておらず、このような場合は、downstreamのmanifest.jsonがなくても処理が続行できるべきだと思います。
dbt_loom.config.ymlのoptionalオプショナルをtrueにすることで、その挙動に設定することができます。

    optional: true

(私が機能追加しました。褒めて)

その他

  • manifest.jsonはローカル他、S3やSnowflakeのStageに置いたものを参照することもできます
  • dbt-loomはdbt_pluginというdbt-core機能に乗っかっており、dbt_loom自体のコードは割とシンプルです。よしんばdbt_loomがメンテナンスされなくなってもフォークして管理しやすいのは安心材料かと思います

Discussion