dbt-osmosisの運用問題について考える

追記

以下、色々書いていますが、dbt-osmosisの作者に課題感を共有した上でそれを解決したPull Requestを取り込んでもらい、よりシンプルに解決できるようになりました。

meta.osmosis_keep_descriptionを付与した上で--force-inheritanceを使えば意図通りに運用できます。

背景: dbt-osmosisを運用に乗せたい

  • 少し前にdbt-osmosisを紹介するエントリを書いた
    • データリネージを考慮しながら、メタデータの伝播をしてくれる便利なツール
  • しかし、運用に乗せようと思うと、これだけだと足りない点があり、まだ運用に乗せ切れていない...
    • 「親のメタデータが更新されたときに子どものメタデータをどう更新するか」問題
  • データカタログを強化していきたい機運が高まっており、そうするとカラムのメタデータの役割は非常に大きい
    • dbt-osmosisを運用に乗せるために必要な残りのパーツを埋めていきたい

運用時に考慮するべき課題

dbt-osmosisの運用時に考えないといけない課題は一言でいうと「親のメタデータが更新されたときに子どものメタデータをどう更新するか」だと思っている。

以下のようなカラムのリネージがあったとしよう。

graph LR;
  X --> Y;
  X --> Y';
  Y --> Z;
  Y' --> Z';

dbt-osmosisを最初に動かす場合、Xにさえdescriptionを付与すれば、YやZにもdescriptionが伝播される。この場合はよいし、dbt-osmosisの便利なところである。

問題はXが更新された場合だ。カラムのdescriptionが更新されるのは、例えば

  • 「5はXXXを意味する」といったようなenum的な意味の追加
  • 「このカラムはdeprecatedになった。代わりにあのカラムを使って欲しい」といった注意喚起
  • 「2023/07/01以降はnullableになった」などデータの仕様の変更に対する注意書き

などのケースだ。こういった情報はYやZにも伝播して欲しいし、これらが漏れるとデータマートの利用者が困ることが出てくるだろう。

dbt-osmosisはYやZにすでに値が入っていれば、ディフォルトでは特にrewriteしない。dbt-osmosisはrewriteさせるオプション--force-inheritanceが存在するので、例えば以下のようなコマンドを打つと、Xが更新されていればYやZも更新される。

% dbt-osmosis yaml refactor --force-inheritance --fqn my_model

force-inheritanceオプションで解決しない問題

--force-inheritanceで全てが解決すればよいのだが、残念ながら世の中はそんなに簡単ではない。Xが更新されていれば、基本的にはYやZも更新して欲しいが、そうでないケースがあるからだ。例えば、以下のようなケースだ。

  • xxxというカラムがDWHで定義されていて、データマート側でも同じxxxというカラムがあるが、定義が若干変わっておりその旨をデータマート側のカラムのdescriptionに反映したい
    • 例: MY_FUNCTION(xxx) AS xxxのように、ちょっと処理をして同名にrenameする
  • 日毎のユーザー数を表わすusers_countというカラムがあるが、月毎に集約した別テーブルにusers_countというカラムがある
    • 「日毎の」や「月毎の」といった意味をdescriptionに反映させたい
    • この例はテーブル名から類推して欲しいのでちょっと微妙だが、集約したい場合などで意味が変わることは他の条件(フィルタの条件が変わるなど)が変わることも考えるとそれなりにあると思う

こういったケースでは--force-inheritanceを実行してしまうと、子ども側のカラムのdescriptionの情報が必要以上に上書きされてしまうので、大変困る。

解決方法: dbtのmetaを使って上書きさせない情報をフィールドに追加する

上記の課題を考慮すると、以下のような解決方法が考えられるだろう。

  • 解決方法1: --force_inheritanceしつつ、カラムによっては無視できるオプションをdbt-osmosis側に実装する
    • 将来的にはうれしそう
    • すぐに取り込んでもらえるかは分からない
  • 解決方法2: --force_inheritanceは使わず、親側が更新されたら追従して欲しいカラムのdescriptionを事前に削除しておく
    • 削除後にdbt-osmosis yaml refactorを動かせば削除された部分を再度親の値で埋めてくれる
    • dbt-osmosis側にprを取り込んでもらう必要はない
    • 手動で削除して回るのは現実的ではないが、yqなどスクリプトを組み合わせれば可能

今回はアイディアの筋がよいかをさっと試したり、フィードバックをもらいたいのが目的のため、解決方法2でやってみることにする。

解決方法2を実現するためには「このカラムは特殊な処理が入っているので、親側が更新されても更新しない」という意図を示す情報をyamlファイルに落とし込む必要がある。dbtで独自のフィールドを追加する方法として、metaというものがある。

例えば、カラムのmetaosmosis_keep_descriptionというフィールドを生やし「このカラムは保持してくれ」という情報を追加する。

version: 2
models:
  - name: my_model
    columns:
      - name: my_column
        description: このモデル専用のロジックがこのカラムには含まれているよ!!
        meta:
          osmosis_keep_description: true

ちなみに、このmetaの情報はdbt docsにも反映されるので「親のdescriptionを引き継いだものか、このmodel特有のものか」を判断することができる。残念ながらBigQueryに反映されるわけではない(タグテンプレートでタグとしてDataplexに送り込むなど、やりようはあるとは思う)。

osmosis_keep_descriptionが付いていないカラムのdescriptionを一括で削除したい場合、yqコマンド*1を使うと簡単にyamlファイルを書き換えてくれる。

yq --inplace '(.models[].columns[] | select(.meta.osmosis_keep_description != true)) |= del(.description)' models/my_model.yml

このコマンドを実行した上で、dbt-osmosisを--force-inheritanceなしで実行すれば「保持したいカラムのdescriptionはそのままで、そうでないカラムは親のdescriptionを引き継ぐ」ということが実現できる。

% dbt-osmosis yaml refactor --fqn my_model

まとめ

dbt-osmosisは便利だが、運用上「親のメタデータが更新されたときに子どものメタデータをどう更新するか」問題をどうするか考える必要がある。今回はdbtのmetaを使ってこの問題を回避する方法を提示してみた。

この方法でしばらく運用してみて筋がよさそうであれば本家のdbt-osmosisにPull Requestを送ってみようと思うし「こっちの方法がいいんじゃない?」とか「うちではこう対処しているよ」などがあれば是非コメントなどで教えて欲しい。

*1:yq、いつの間にかpythonからgoに変わっていた