Yappli Tech Blog

株式会社ヤプリの開発メンバーによるブログです。最新の技術情報からチーム・働き方に関するテーマまで、日々の熱い想いを持って発信していきます。

dbt-osmosisを導入してdbt docsのdescriptionの拡充率を30%→80%まで上げた話

こんにちは! データサイエンス室の山本(@__Y4M4MOTO__)です。

ヤプリではdbtを用いてデータ基盤を運用しており、dbt docsをデータカタログとして使用しています。

dbt docs整備の話はこちらの資料にて公開しているので、そちらをご覧いただけましたら幸いです。 speakerdeck.com

ヤプリのdbt基盤には主にアプリユーザーの行動ログやアプリプラットフォーム「Yappli」のサービスDBのデータが入っています。これらのデータをSource, Staging, Component, Martの4つの層で加工しています。当時、dbt docsのdescriptionが埋まっていたのはSource層のテーブルのみで他の層はほとんど埋まっておらず、拡充率としては30% ほどでした。

そこで、dbt-osmosisというツールを導入して拡充率を80% まで引き上げました。今回は、その導入過程と、その結果についてお話しします。

dbt-osmosisによるdbt docs拡充のイメージ(上記資料から抜粋)

dbt-osmosisとは?

公式リポジトリはこちら。

github.com

dbt-osmosisはYAMLファイル(ソースYAMLファイル、スキーマYAMLファイル)を自動生成したり、上流テーブルのカラムのdescriptionなどを下流へ伝搬させたりするツールです。

これにより、同じカラムであれば上流テーブルのカラムのdescriptionさえ記載・更新すれば、下流テーブルのカラムのdescriptionも自動で更新されるため、効率的にメタデータを管理できます。

dbt-osmosisの有用性は次のスライドのp19~21がわかりやすいです。

dbt-osmosisの具体的な使い方については次の記事にまとめていますので、そちらをご覧ください。

qiita.com

導入方針

導入にあたって悩んだのが「dbt-osmosisの実行結果をGit管理するかどうか」です。

これを決めるにあたって、管理した場合としなかった場合でそれぞれどのような運用になるのかを考え、それぞれメリット・デメリットを洗い出しました。

はじめに: 当時のdbt docsの運用フロー

メリット・デメリットについて述べる前に、当時のdbt docsの運用フローについて簡単に説明します。

ヤプリのdbt基盤では主に次の3種類のブランチを使用しています。

  • topicブランチ ... 新規開発・既存改修を行なうブランチ。チケットごとに作られる
  • developブランチ ... 開発の主軸となるブランチ
  • mainブランチ ... 本番環境のコードを置いておくブランチ

これらのブランチは次のようなフローで運用されています。

sequenceDiagram participant main develop->>topic: checkout -b topic->>topic: commit
(新規開発・既存改修) topic-)develop: Pull Request develop-)main: Pull Request
(本番リリース)

本番リリースのPRがmergeされたタイミングで、GitHub Actionsを用いてGitHub Pagesへdbt docsをデプロイするようにしています。

GitHub Actionsのワークフローでは次のような流れで処理を行なっています。

  1. mainブランチへのpushが行なわれたらワークフローを実行
  2. ワークフロー内にdbtの実行環境を作成
  3. dbt docs generate コマンドを実行してdbt docs用のファイル(index.html , catalog.json , manifest.json , assets/*)を生成
  4. 生成されたファイルをGitHub Pagesへデプロイ

Git管理する場合

メリット

✅ dbt-osmosisの実行結果をPRレビュー等でチェックできる

dbt-osmosisの実行結果をGit管理するということは、当然その内容をPRでチェックすることになります。これにより、dbt-osmosisの実行結果に誤りがあった場合にPRレビューで指摘できるため、早期に修正できます。

✅ 本番のdbt docsへの反映方法に変更を加えなくて済む

dbt-osmosisの実行結果はPRを通してすでに取り込まれているはずであるため、これまでどおり本番のdbt docsへ反映すればOKとなります。

デメリット

❌ 一度書き込まれたdescriptionを再伝搬させるのが困難

dbt-osmosisの伝搬はdescriptionが空の場合に行なわれます。dbt-osmosisの実行結果をGit管理する場合、初回実行以降はdescriptionが埋まった状態となります。そのため、既存のdescriptionを変更する必要があった場合にそのままでは再度伝搬させることができません。

こういった場合に対応できるよう、dbt-osmosisには --force-inheritance というオプションが用意されています。このオプションを指定すると、すでにdescriptionが埋まっていてもその内容を上書きするようになります。これにより再伝搬が可能となります。

ただし、同じカラム名でも微妙に定義などが異なり、その旨がdescriptionに追記されていた場合、それらは上書きによって消されてしまいます。その場合、次のように meta:osmosis_keep_description: true という設定を追加することでdescriptionの上書きを抑制できます。

- name: first_name
  description: "ここだけ手動変更"
  data_type: INT64
  meta:
    osmosis_keep_description: true
このあたりは次の記事が詳しいため、こちらをご覧ください。 www.yasuhisay.info

ただし、dbt-osmosisは meta: の内容も伝搬するため、 osmosis_keep_description: true も伝搬されます。そのため、 osmosis_keep_description: true を設定したカラム以降のカラムについては再伝搬ができなくなります。

2024/07/18追記
Xのリプライにて補足をいただきました!(ありがとうございます…!🙏 )
meta:の伝搬については--skip-merge-metaオプションを利用することで伝搬させないようにできるようです。 product.10x.co.jp

❌ YAMLファイルの行数が多くなり、人間がメンテナンスするにはつらい

dbt-osmosisを実行すると全テーブル全カラムについて、次のような name:description: の2項目がYAMLファイルへ書き込まれます。

- name: customer_id
  description: "hogehoge"

そのため、YAMLファイルの行数がかなり多くなります(例えば、10カラムあるテーブルが10個あったら2 × 10 × 10 = 200行になります)。今後、手動でdescriptionを追加・修正する際に、膨大な行の中から目的の行を探すのはかなりの負担になることが予想されます。実際にdbt-osmosis実行後のYAMLファイルを手動更新してみましたが、地味ながら想像以上の負担でした。

dbt-osmosisではdbtモデルごとにスキーマYAMLファイルを分割させることもできるので、dbt-osmosisの実行結果をGit管理したい場合はそうした方が良さそうです。ただし、ファイル数が2倍に増えるため、その点については考慮する必要がありそうです。

❌ dbt-osmosisの実行し、その結果を取り込むワークフローを組む必要がある

dbt-osmosisの実行結果を取り込むために、どこかのタイミングでdbt-osmosisを実行する必要があります。手動実行するのは運用面で現実的ではないのでGitHub Actionsなどを使って自動化することになります。

当時のdbt docsの運用フローを踏まえ、Git管理する場合は次の2か所のいずれかが自動化タイミングになると考えました。

  • topicブランチ → developブランチへのPR作成時にdbt-osmosisを実行し、その結果をPRに含める
  • topicブランチ → developブランチへのPRがマージされたときにdbt-osmosisを実行し、その結果を取り込むためのPRを作成する

Git管理しない

メリット

✅ YAMLファイルには手動入力した分の記載しかないため、人間がメンテナンスしやすい

YAMLファイルに記載されるのは新たに追加したカラムや同名だが定義が変わっていて要補足なカラムのdescriptionが主となるため、人間でもメンテナンス可能な行数に収まります。

✅ 既存の開発フローに取り入れやすい

基本的に本番のdbt docsを更新するワークフローにdbt-osmosisを実行するステップを追加するだけで済むため、dbt基盤の開発メンバーに対して開発フローの変更を強いることなく導入できます。

✅ descriptionの再伝搬が容易

dbt-osmosisの実行結果をGit管理しないことで、常にdbt-osmosisを初回実行している状態となります。これによりdescriptionの再伝搬も容易に実現できます。

デメリット

❌ 本番反映前にdbt-osmosisの実行結果についてチェックすることが困難

dbt-osmosisの実行結果をGit管理しないため、どこかでチェックするフェーズを設けない場合、dbt-osmosisの実行結果が確認できるのは本番のdbt docsへ反映された後となります。

本番前にチェックするには別途dbt docs確認用の環境を用意する必要があります。しかし、dbt docs確認用の環境を新たに用意するのは意外と手間がかかります。

dbt docsの確認環境を用意方法として次の3つの方針が考えられます。

  • ① GCSやS3にHTTPSとしてホスティングする
  • ② 別のGitHubリポジトリを用意し、そのリポジトリのGitHub Pagesにホスティングする
  • ③ ローカルで確認できるようにする

当初は①の方針を検討していましたが、社内公開に限定するためのアクセス制限に必要となる工数が予想以上だったため、断念しました。

続いて ② の方針も検討しましたが、dbt docsが確認できるメリットより運用が複雑になるデメリットの方が大きいと判断し、こちらも断念しました。

結果、Git管理しない場合は ③ の方針で進めるのが良いと判断しました。

どう導入したのか?

「Git管理する/しない」でメリット・デメリットを比較した結果、「Git管理しない」ことに決めました。決め手になったのは次の点です。

  • Git管理する場合のデメリット: ❌ 一度書き込まれたdescriptionを再伝搬させるのが困難
  • Git管理しない場合のメリット: ✅ descriptionの再伝搬が容易

メタデータへの追記・訂正が容易に行なえることは、有用なデータカタログを目指す上で欠かせないと考えました。

  • Git管理する場合のデメリット: ❌ YAMLファイルの行数が多くなり、人間がメンテナンスするにはつらい
  • Git管理しない場合のメリット: ✅ YAMLファイルには手動入力した分の記載しかないため、人間がメンテナンスしやすい

メタデータの追加・更新は人間が行なうことになるため、人間にとってメンテナンスしやすいかどうかは重視したいと考えました。

以上の判断をもとにdbt-osmosisの導入を進め、最終的に次の2か所に取り入れました。

  • 本番のdbt docs
  • ローカルのdbt docs

本番のdbt docs

dbt docsをGitHub PagesへデプロイするためのGitHub Actionsワークフローの流れを再掲します。

  1. mainブランチへのpushが行われたらワークフローを実行
  2. ワークフロー内にdbtの実行環境を作成
  3. dbt docs generate コマンドを実行してdbt docs用のファイル( index.html , catalog.json , manifest.json assets/* )を生成
  4. 生成されたファイルをGitHub Pagesへデプロイ

ここの手順3. のところでdbt-osmosisを実行するようにしています。ワークフローファイルから該当ステップを抜粋したものを次に示します。

- name: Generate dbt document
  run: |
    source venv/bin/activate
    dbt-osmosis yaml refactor --target prod --skip-add-data-types --add-progenitor-to-meta
    dbt docs generate

dbt-osmosisを実行してYAMLファイルを更新した後に dbt docs generate コマンドを実行することで、カラム情報が伝搬した後のメタデータでdbt docsが生成されます。

dbt-osmosisの実行は dbt-osmosis yaml refactor に次の3つのオプションをつけて行なっています。

  • --target prod
  • --skip-add-data-types
  • --add-progenitor-to-meta

--target prod は本番用のdbtプロファイルを使用する(=本番テーブルのメタデータを使用する)ことを意味しています。dbt-osmosisではローカルのYAMLファイルだけでなくデータウェアハウス上(ヤプリの場合BigQuery上)のテーブルのメタデータも使用します。今回は本番のdbt docsを更新したいため、本番用のプロファイルを使用するよう指定しています。

--skip-add-data-types はYAMLファイルに data_type: INT64 のようなデータ型を追記しないことを意味しています。データ型はYAMLファイルに記載しておかなくてもdbt docsに載るため、処理をスキップする目的で指定しています。

--add-progenitor-to-meta はカラムの伝搬元となるモデルを追記することを意味しています。これにより、カラムのdescriptionを追加・修正する必要があった際にどのモデルのカラムに対して行なえば良いかがわかります。 伝搬元は次のように meta:osmosis_progenitor: という項目に記載されます。

      - name: customer_id
        description: ''
        meta:
          osmosis_progenitor: model.my_dbt_project.stg_customers

ローカルのdbt docs

ローカルでもdbt-osmosis実行後のdbt docsを確認できるようにしました。これにより、 開発段階でdbt-osmosisの実行結果をチェックでき、前述のデメリットを解消しました。

確認は次の手順で行なえるようにしました。

  1. YAMLファイルに変更を加える
  2. 変更をコミットする
  3. make docs-generate というコマンドを実行する

あらかじめ変更をコミットしておく必要がありますが、 dbt docs generate コマンドと似たような感覚で使えるようにしました。

手順3. ではMakefileに定義したこれらのコマンドを実行しています。

.PHONY: docs-generate
docs-generate:
    # 後で戻って来れるようカレントブランチを取得
    $(eval CURRENT_BRANCH=$(shell git branch --show-current))

    # dbt-osmosis を実行するためのブランチを新規作成して checkout
    git checkout -b review-dbt-docs

    # カレントブランチの変更内容を review-dbt-docs へ取り込み
    git merge --no-commit $(CURRENT_BRANCH)
    git commit --allow-empty -m "Merge $(CURRENT_BRANCH) into review-dbt-docs"

    # dbt-osmosis でカラム情報を流して artifacts を更新
    dbt-osmosis yaml refactor --target prod --skip-add-data-types --add-progenitor-to-meta
    dbt docs generate --target prod

    # 役目を終えた review-dbt-docs ブランチを処分して元のブランチに戻る
    git checkout .
    git checkout $(CURRENT_BRANCH)
    git branch -D review-dbt-docs
2024/08/12追記
上記Makefileですが、下記の修正を行いました。
      dbt-osmosis yaml refactor --target prod --skip-add-data-types --add-progenitor-to-meta
-     dbt docs generate
+     dbt docs generate --target prod
理由は、 `dbt docs generate` でもtargetを指定しておかないと、デフォルトtargetを用いて(=dev環境のDBを参照して)dbt artifactsが生成されてしまうためです。

dbt-osmosisは既存のYAMLファイルに変更を加えます。dbt-osmosisが加えた変更はGit管理しないようにしたかったので、dbt-osmosisを実行するためのブランチを別途用意するようにしました。これにより、既存のYAMLファイルに変更を加えることなくdbt-osmosisの実行結果をdbt docs上で確認できるようになります。

ただし、この方法には次の注意点があります。

  • コミット前に make docs-generate コマンドを実行すると未コミットの変更を消し飛ばしてしまう可能性がある
  • make docs-generate コマンド内でエラーが出るとdbt-osmosis実行用ブランチにとどまってしまうため、現状復帰に一手間かかる
    • 「一手間」=元のブランチに戻ってdbt-osmosis実行用ブランチを削除

この辺りは今後改善していければと思っています。

やってみた所感

dbt-osmosisで拡充できる範囲には限界がある

dbtモデルのカラムは上流モデルから流用されているものばかりではありません。途中で新しく追加したカラムや定義が変わったカラムなども存在します。そういったカラムのdescriptionは引き続き人力で埋める必要があります。

こちらについてはdbt-osmosisの公式ドキュメントでも触れられています。

z3z1ma.github.io

今回の件でも、dbt-osmosis導入直後のdbt docsの拡充率は60%ほどで、そこから人力でカラムを埋めることで80%まで引き上げています。

osmosis_progenitor: に記載されている伝搬元が誤っている場合がある

2024/07/18追記
Xのリプライにて補足をいただきました!(ありがとうございます…!🙏 )
こちら、不具合ではなく仕様のようです。
dbt-osmosisは伝搬元をクエリを解析して決定しているわけではなく、ノードの距離で決める仕様とのことです。そのため、ノードの距離が同一の場合は正確に判断できなくなります。
github.com

どのような場合に誤ってしまうのかはまだ検証中ですが、誤ってしまう場合の一例を以下に示します。

次のようなリネージグラフのモデル群があるとします(fct_item_shops のところの A ~ C はJOINする順でアルファベットを割り当てています)。

classDiagram class raw_shops { shop_id item_key } class raw_items { item_key item_code } class raw_item_shops { item_code shop_id } class stg_shops { shop_id item_key } class stg_items { item_key item_code } class stg_item_shops { item_code shop_id } class fct_item_shops { C.item_key A.item_code A.shop_id } %% raw_shops --> stg_shops raw_items --> stg_items raw_item_shops --> stg_item_shops stg_shops --> fct_item_shops: LEFT JOIN shops B
USING (shop_id) stg_items --> fct_item_shops: LEFT JOIN items C
USING (item_code) stg_item_shops --> fct_item_shops: shop_items A

実際のコードは次のリポジトリにあるのでそちらをご覧ください。

github.com

この場合、 fct_item_shops の各カラムの伝搬元はそれぞれ次のようになっているはずです。

  • item_key ... raw_items
  • item_code ... raw_shop_items
  • shop_id ... raw_shop_items

しかし、実際の結果は次のようになっており、 item_code の伝搬元が raw_shop_items ではなく raw_items となっています。

  • item_key ... raw_items
  • item_code ... raw_items ← raw_shop_items のはずでは…?
  • shop_id ... raw_shop_items

dbt-osmosisの実行結果

version: 2
models:
  - name: stg_shops
    columns:
      - name: shop_id
        description: ''
        data_type: INT64
        meta:
          osmosis_progenitor: source.my_dbt_project.tmp_dbt_osmosis_test.raw_shops
      - name: item_key
        description: ''
        data_type: STRING
        meta:
          osmosis_progenitor: source.my_dbt_project.tmp_dbt_osmosis_test.raw_shops
  - name: stg_item_shops
    columns:
      - name: item_code
        description: ''
        data_type: STRING
        meta:
          osmosis_progenitor: source.my_dbt_project.tmp_dbt_osmosis_test.raw_item_shops
      - name: shop_id
        description: ''
        data_type: INT64
        meta:
          osmosis_progenitor: source.my_dbt_project.tmp_dbt_osmosis_test.raw_item_shops
  - name: stg_items
    columns:
      - name: item_key
        description: ''
        data_type: STRING
        meta:
          osmosis_progenitor: source.my_dbt_project.tmp_dbt_osmosis_test.raw_items
      - name: item_code
        description: ''
        data_type: STRING
        meta:
          osmosis_progenitor: source.my_dbt_project.tmp_dbt_osmosis_test.raw_items
  - name: fct_item_shops
    columns:
      - name: item_key
        description: ''
        meta:
          osmosis_progenitor: source.my_dbt_project.tmp_dbt_osmosis_test.raw_items
        data_type: STRING
      - name: item_code
        description: ''
        meta:
          osmosis_progenitor: source.my_dbt_project.tmp_dbt_osmosis_test.raw_items
        data_type: STRING
      - name: shop_id
        description: ''
        meta:
          osmosis_progenitor: source.my_dbt_project.tmp_dbt_osmosis_test.raw_shops
        data_type: INT64

暫定対応として、descriptionに正しい伝搬元を手動で記載しています。

この現象についてはdbt-osmosis公式リポジトリにissueとして報告しています。原因と解決策がわかったらPull Requestを作成したいと思います。

github.com

結び

この記事ではヤプリのdbt基盤にdbt-osmosisを導入した話について記しました。この記事がdbt-osmosis導入の参考になれば幸いです。

ここまでお読みいただきありがとうございました!この記事を読んでヤプリのデータ基盤開発に興味が出た方、ぜひカジュアル面談でお話ししましょう!

open.talentio.com