こんにちは、データプラットフォームチームの鳥山(@to_lz1)です。
本記事はdbt Advent Calendar 2024の10日目の記事です。
データ基盤を扱う上で避けて通れない「メタデータの管理」ですが、あなたのチームではどのように取り組まれているでしょうか?独自メタデータカタログを作ったけど、運用がつらくなっているとか、はたまた構築初期なのであえてdescriptionなしのまま突き進んでいるなど、データ基盤整備のフェーズによっても各社さまざまかと思います。
弊社も例に漏れず上記のようなフェーズを通過してきましたが、最近ではdbtやその周辺ツールを使った効率化がうまく回り始めてきました。本記事ではその仕組みをどのように構築したのかをご紹介します。
そもそもメタデータとは?
メタデータとは「あるデータが付随して持つそのデータ自身についての付加的なデータ」1 を指します。
例えばある加工テーブルを使って分析するとき、「そのテーブルは何を目的に作られたのか、1レコードの粒度はなにか」や「それぞれのカラムは数値なのか文字列なのか、あるいはいつ更新されたデータなのか」といった情報がないと、適切な扱い方がわかりません。
このように「データに関するデータ」がデータ活用のために重要である、という認知はここ数年で大きく広まりました。個人的にも「メタデータ」に関するトピックやツール、記事がデータエンジニアの間でもよくシェアされるようになった実感があります。
データ基盤文脈では、メタデータは
- テクニカルメタデータ(データ型、キーなど)
- ビジネスメタデータ(ビジネス用語の意味、データセット管理部署など)
- オペレーショナルメタデータ(バッチジョブの実行履歴など)
の3区分に大きく分けて論じられることが多いですが、本記事ではこのうちビジネスメタデータ、特に煩雑になりがちな「テーブルと列のdescription」の入力をどのように負荷軽減していったかに焦点を当てます。
旧来の仕組み
過去のブログ記事でも紹介した通り、Classiでは2024年初頭にdbtを導入しました。
dbtには dbt docs
というデータに関するドキュメントを自動生成できるコマンド2があり、弊社でもdbt導入初期からメタデータカタログをホストしていました。
しかし、これでメタデータのマネジメントが完了か?というとそんな事は全くありません。
特に導入初期では以下のような課題がありました。
dwhのような加工テーブルは頻繁に変更・追加がある。影響するテーブルまでdescriptionをメンテナンスするのは面倒、かつ漏れが起きやすい
最上流のRDBデータのdescriptionは一切入っていない。「dwhのdescriptionで書かれている列の、更にその定義」を知りたくなったときに情報が得られない
新しい仕組み
Classiのデータプラットフォームチームでは、以下の改善を順次導入していくことでこうした課題を解決していきました。
- dbt-osmosisの導入
- dbt-source-importerの導入とCI化
- 大元のRDBからのメタデータ吸い上げ
以下、順番に説明していきます。
dbt-osmosisの導入
dbt-osmosisはyamlの管理やdiffの確認といった運用作業を支援してくれるツールです。
特に dbt-osmosis yaml refactor
コマンドが非常に強力で、上流のテーブルにdescriptionが入っていれば、下流の加工テーブルまでそのdescriptionを伝播して更新してくれます。
このツールを早期導入することで、「dwhを改修したとき、影響するテーブルまでdescriptionをメンテナンスする」といった手間がなくなりました。
dbt-source-importerの導入とCI化
dbtのyamlに「すでに書いてあるメタデータ」であればdbt-osmosisだけで十分ですが、書いてないメタデータについてはどうにかしてそれを埋めていかなければなりません。
このうち、最上流のRDBデータに関しては、ClassiのアーキテクチャではBigQueryにLoadするときにJSONに書いておけばそれがそのままテーブルの各列のdescriptionとして反映されます。単純化したJSONファイルとPythonコードは以下のようなものです。
[ { "mode": "REQUIRED", "name": "id", "type": "STRING", + "description": "XXのID" }, { "mode": "NULLABLE", "name": "title", "type": "STRING", + "description": "タイトル" } ]
from google.cloud import bigquery storage_client = storage.Client(project=XXXX) bucket = storage_client.get_bucket(BUCKET_NAME) blob = bucket.blob(JSON_PATH) with blob.open("r") as f: schema = json.load(f) bigquery_client = bigquery.Client() job_config = bigquery.LoadJobConfig( schema=schema, source_format=bigquery.SourceFormat.NEWLINE_DELIMITED_JSON, write_disposition=bigquery.WriteDisposition.WRITE_TRUNCATE, max_bad_records=0, autodetect=false, ) load_job = bigquery_client.load_table_from_uri( source_uris=DATA_FILE_URIS, destination=DESTINATION_TABLE_ID, job_config=job_config, )
仕組みがあるなら埋めればよいだけなので、今年の春ごろに社内メタデータカタログに書いてあった内容をすべてJSONに移植しました。これで、かつて社内メタデータカタログに書いてあったことはすべてBQコンソールで見れるようになりました。
ただし、これだけだとdbt docsの方にはdescriptionが反映されないので、dbt-source-importerというOSSも併用しました。こちらは前回の記事でもご紹介したとおりです。
また、このタイミングでdbt-source-importerとdbt-osmosisを週次で自動実行するようにしました。これは結果的にとても良い判断だったと思っています。
メタデータは自動でどんどん最新化してくれると良いので、最初に思い浮かぶのは「Pull Requestを出したときのCIに組み込む」というものだと思います。実際私も最初そうしようとしました。
しかし、dbt-osmosisのyaml refactorはデフォルトでは全modelを走査するので爆速とはいきません。変更があったモデルだけ検出しても良いですが、わざわざそのためにロジックを考えるのも少し面倒です。
また、devにしかない新規テーブルがある状態でprodに対してosmosisを流すと落ちたりするなど、環境間の差異をどう吸収すると良いのか、というのも地味な悩みどころでした。
ふと立ち止まって考えてみると、メタデータは「常に整合が取れていないと死ぬ」といった類のものではありません。ということで、「『翌週には最新化されている』くらいのスピード感でも良いんじゃないか?」と思いつけたことで、「週に一回dbt-source-importerとdbt-osmosisを実行してcreate-pull-request でPRを出す」という現在の構成に落ち着きました。
結果として、週に1回自動で上がってくるPRを見るだけで良くなったので、整合性の担保が劇的に楽になったと思います。
大元のRDBからのメタデータ吸い上げ
先ほど社内メタデータカタログからデータを吸い上げた、という話を書きましたが、それでもまだ書かれていないdescriptionはたくさん残っています。こうしたものについてはやはり地道に埋めるしかないのでしょうか?
いいえ、まだ他の手段があります。RDBの世界であれば、大元のスキーマ定義(例: Railsであればschema.rb)にコメントが書いてあればそれを正と見なした方が効率が良いです。DBにもよりますが、Classi内にはすでにこうしたコメントが充実しているテーブル群もあったので、使わない手はありません。
Classiのデータ基盤はもともとschema.rb等を読み取って中間出力ファイル群を生成するアーキテクチャになっていたので、「ここで列コメントも読み取ってJSONに反映するようにできないか?」と思い立ち、RDBのスキーマファイルにコメントが入っていればそれが自動でBigQueryにも反映されるような機能を実装しました3。
最終的な形
諸々の変遷を経て、以前のブログで計画していたようなフローが概ね実現できたかなと思います。
- RDBにコメントがあれば、それがBQに自動反映される
- RDBのカラムに特記事項があれば、分析者側で付け足すこともできる
- そのカラムを直接使ったdwhであれば、dbt-osmosisで説明が自動反映される
- 加工テーブルにしかないカラムの説明は、model yamlに書けば良い(=これは開発時のPRで同時にレビューすれば良い)
- 加工テーブルのカラムを二次利用した場合も、dbt-osmosisで説明が自動反映される
という寸法で、拡張も運用もかなり楽になりました。
今後の展望
手作業をかなり減らせはしましたが「schema.rbが大元のシステム側で変わったらコピーしてこないといけない」といった最後のtoilが残っているので、この辺りも空き時間を見つけながらチームで自動化を進めています。
また、これらメタデータが本当にきちんと使えてもらえるのか?というモニタリングも欠かせません。特に、Classi内ではBIツールの利用が盛んですが、TableauもRedashもBQ上にあるdescriptionが引き継がれないため、エンドユーザから見た利便性が大きく損なわれており残念なところです。個人的にはここがメタデータ活用のラストワンマイルだと思っており、ツールの見直し等も視野に再検討してもいいかもと思います。
以上、dbt周辺ツールを使ったメタデータ管理の事例をご紹介しました。皆さまのデータ基盤整備・改善に少しでも参考になる所があれば幸いです。
- https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BF%E3%83%87%E3%83%BC%E3%82%BF (2024-12-06時点)↩
- https://docs.getdbt.com/reference/commands/cmd-docs↩
- ちなみに、この機能は弊社24年度新卒メンバーのデータエンジニア短期留学の一環として実装してもらいました。本人の別記事はこちらでご覧いただけます。↩