Data Vaultを3年以上運用して得た学び – yasuhisa’s blog

背景: Data Vaultを運用し続けている事例の公開は多くない

  • Data Vaultを運用して3年以上が経過した
  • 世の中ではData Vaultをやっている会社は(少なくとも日本では)そもそも少ない
  • 「Data Vaultをやってみました」エントリは少ないながらも存在するが、数年単位で運用してみた上でのエントリはほとんどない
    • その上で、運用してみた上で事業や運用の特性上やめたというmashiikeさんの発表は貴重な事例
  • 現職で3年以上Data Vaultを運用しているので、そこから得た学びを書いてみようと思う
    • 以下のスライドのdbt(v0.2)と記載されている範囲がData Vaultと思ってもらってほとんど問題ないです
  • これに加え、新規事業でデータプロダクトの運用が始まっており、こちらもData Vaultで運用をしています
    • が、本エントリでは主にデータ分析基盤に関するData Vaultをスコープとします

おさらい: Data Vaultとは何か

改めて説明はしませんが、簡単におさらいをしておくと

  • Hub:
    • データ分析において、データ内に何が存在 / 定義されているかを分かるのは重要
      • 「テーブルのdescriptionがメタデータとして記載されていて」という話ではないです
    • つまり、ビジネスオブジェクトやEntityを定義したい
    • もっと端的に言えば、ビジネスキーを定義するものがHubとも言える
  • Link:
    • 時間を経ると、あるビジネスオブジェクトと別のビジネスオブジェクトの関係性は変化する
    • 例: ある時点では顧客Aは店舗Xを利用していたが、途中から引越しに伴ない店舗Yを利用するようになった
    • こういったビジネスオブジェクト間の関係性を記録するものがLink
  • Satellite:
    • データ分析はデータの最新の状態だけでは不十分なことが多い
    • 例: 地域別での売上の遷移を知りたい。顧客は定期的に引越しをすることがあるため、任意の時点での住所を知る必要がある
    • こういったビジネスオブジェクトの状態の履歴を記録するものがSatellite

となります。強調しておきますが、「Data Vaultで実装するのに必要なものがHub / Link / Satellite」ではなく「データ分析などビジネスの要求に答えるのに必要な要素やエッセンスを抽象化 / 汎用化したものがHub / Link / Satellite」と捉えるのがよいでしょう。

データ分析基盤におけるData Vault

分かりやすいように、私たちが運用しているデータ基盤のレイヤリングも紹介しておきます。

graph LR;
  sources --> raw_stage;
  raw_stage --> stage;
  stage --> raw_vault;
  raw_vault --> business_vault;
  raw_vault --> dimensionals;
  business_vault --> dimensionals;
  raw_vault --> mart;
  business_vault --> mart;
  • raw_stage:
    • 主にデータ基盤開発者向けの層
    • データの型変換 / タイムゾーンの変換 / ビジネスキーのクリーニングが目的
  • stage:
    • 主にデータ基盤開発者向けの層
    • ビジネスキーの特定や生成が目的
  • raw_vault:
    • 主にデータ基盤開発者向けの層
    • Data VaultのコアになるHub / Link / Satelliteを定義する層
      • ビジネスオブジェクトの特定(Hub)
      • 後段のFactに相当するビジネスオブジェクト同士の繋がりの特定(Link)
      • 履歴の生成(Satellite)
  • business_vault:
    • 主にデータ基盤開発者向けの層
    • ビジネスロジックの実装が必要な場合、ここで実装する
      • ここにビジネスロジックを固めることでSSoTを担保できるようにする
    • 履歴管理を行なわない
      • パフォーマンス観点で必要な場合のみincrementalにする
  • dimensionals:
    • 主にデータ活用者向けの層
    • いわゆるディメンショナルモデリングの成果物となるFactテーブルやDimensionテーブルを配置
    • FactテーブルはLinkやTransactional Linkをベースに構築
    • DimensionテーブルはSatelliteの履歴を頼りに構築
    • 前段でHubを適切に定義しているため
      • FactとDimensionのJOINは問題なくできる
      • 適合ディメンションになっているため、複数のFactテーブル(複数のビジネスイベント)を跨いだ分析も問題なくできる
  • mart:
    • 主にデータ活用者向けの層
    • 大福帳テーブルの提供、ダッシュボードなど要件の決まっているデータマートの提供
    • データユーザー / BIツールなど使う対象は様々

Data Vault自体の解説記事

Data Vault自体の解説は以下の記事を読んでみるとよいでしょう。

Data Vaultを使った開発 with dbt

なお、素のSQLだけでData Vaultを運用しようとすると面倒なことも多いですが、その辺の面倒事をwrapしてくれるAutomateDVを採用しています。AutomateDVは実態としてはdbtのマクロであり、マクロ自体も普通に読めますし*1、compiled queryの中身を見るとやっていることも難しくないので、重厚長大なフレームワークではないです。

Data Vaultを運用してよかったこと

データモデリングを強制される

「強制される」と書くとネガティブと捉える人がいるかもしれませんが、それは違います。例えば、Ruby on Railsが開発効率を強力に上げてくれるのはRails Way(例: 設定より規約)がある種の型や制約を提供してくれているからです。

Railsと同じく、Data VaultもHub / Link / Satelliteという基本の型があります。特にHubとLinkでは以下のことが要求されます。

  • Hubを定義するにはビジネスキーを定義する必要がある
    • ビジネスオブジェクトやEntityの定義を考慮する必要があり、特定のデータマート構築のような限定的な応用ではない頭の使い方が要求される
  • Linkを定義するには対応するビジネスイベントを同定する必要がある
    • ビジネスイベントは単一で考えるというよりも関連するイベントのシーケンスを考慮する必要がある
    • ファネル分析などではプロセスを跨ぐ分析にも対応できる必要があり、Linkを考える上でよりHubの定義が磨き込まれていく
    • 1つのLinkに複数のビジネスイベントが含まれていないかも考慮する必要がある
      • 例: 注文は単一のビジネスイベントではなく「配達完了」や「キャンセル」など複数のビジネスイベントを含んでいる
    • こうした考慮の中でLinkの粒度やイベントに関連する7W(誰が / どこで / いつ / etc)も磨き込まれていく

このようにData Vaultはデータモデリングの過程を経ないと、そもそも定義できません。つまり、データモデリングを強制されます。

データモデリングをせずにデータマートを構築するのは、単発で見ると開発スピードは早いと思われるかもしれません。しかし、こうしたデータマートは

  • ビジネスキーの定義がされていないため、データマート毎に顧客の定義がブレる
    • 他のデータマートと一緒に分析することが難しい
  • イベントの粒度が考慮できていないケースも多く、重複カウントなどの問題が多発する

などの課題が起きやすいです。実際、Data Vault以前のデータ基盤(このスライドでいうところのv0.1やadhoc)ではこうした課題が頻発しており、データ品質の低さに繋がっていました。データ品質が低いということは

  • データ活用者からの問い合わせも多くなる
  • 新しいマートを作る際に使い回せるコンポーネントがないため、似たようなマートのコードのコピペをして開発しようとするも、元のコードの品質が低いためかえって時間がかかる

など、中長期で見た場合の生産性や決して高くありませんでした。Data Vaultは初速こそ出にくいかもしれませんが、中長期では丁寧にデータモデリングされた小さいコンポーネントが充実してくるため、データ品質の高いデータマートなどを作りやすくなります。こういったことは「データモデリングすることを強制される」ことのご利益の一つです。

ディメンショナルモデリングとの相性のよさ

感のよい方ならお気付きかもしれませんが、Data Vaultはディメンショナルモデリングと非常に相性がよいです。FactはLink(やTransactional Link)をベースに構成していくことがほとんどですし、DimenionはSatelliteをベースに構築していけば、ほとんど困りません。Hubの設計がきちんとされているので、適合ディメンションになっていることが多く、複数のビジネスイベントを跨いだ分析も自然な形で可能です。

こう書くと、以下のような疑問を持たれる方がいるかもしれません。

  • 1: Data Vaultがあればディメンショナルモデリングは不要?
  • 2: それだけ似ているのであれば、Data Vaultを経由せずディメンショナルモデリングだけで十分ではないか?

1番目の疑問に対する回答は、明確にNoです。Data Vaultは主にデータエンジニアやアナリティクスエンジニアのためにモデリングされた成果物となるため、データ活用者にとっては正直使い込なすのが難しいです。Data VaultではHub / Link / Satelliteを細かいコンポーネントに分けるため、Data Vaultの成果物を直接データ分析に使おうとすると、非常に多くのJOINが必要になります。一方でディメンショナルモデリングは分析者のためのフレームワークであり、スタースキーマのような基本的な考え方を身に付ければ簡単にクエリを書くことやBIツールからさっと分析できます。

2番目の疑問に対する回答は「場合による」です。我々も以前はData VaultなしでFactやDimensionの成果物をデータ活用者に提供していました。しかし、一定期間運用していく中で、以下のような課題に直面しました。

  • 表層だけのディメンショナルモデリング
    • fact_hogedim_fugaのような一見するとディメンショナルモデリングっぽく見えるが実は見た目だけ
    • 粒度がなんとなく決まっているだけで、活用しようとすると重複カウントが起きる
    • ビジネスキーの設計が適当でうまくJOINできない
      • ドリルアクロスなどの分析もしにくい / できない
  • intermidiate地獄
    • 成果物に必要なビジネスロジックを置く場所としてintermidiateディレクトリにSQLが散乱する結果となり、非常に見通しが悪い
    • intermidiateディレクトリの中を整理したいが、整理するためのよい観点が自明ではない

もちろん、こうしたことを避けるためのプラクティスは世の中にすでにいくつか存在します。

  • Kimball GroupのDimensional Modeling Techniquesでは、Dimensional Design Processとして以下が定義されています
    • 1: Select the business process
    • 2: Declare the grain
    • 3: Identify the dimensions
    • 4: Identify the facts
  • 書籍アジャイルデータモデリングでは、ビジネスイベント / ビジネスディメンション / ビジネスプロセスのモデリングを行なう方法として、以下が紹介されています
    • BEAM*
    • ディメンショナルマトリックス
    • イベントマトリックス

しかし、こういったプラックティス内であっても、チーム開発で迷わないような具体的な方法論は薄いという印象があります。Data Vaultはそういった知見をより体系化しており、ディメンショナルモデリングをより機能させる存在であると感じています。

アリジティが高く、後から拡張ができる

これはData Vaultの特徴でよく言われることの一つではあります。分析用のデータ基盤を運用していると、以下のように後から情報が増えてくることはよくあります。

  • 1: 最初は顧客に関する情報はRDBのみが保持していた
    • 最初はhub_usersat_userのみで済んでいた
  • 2: 途中からCRMの導入が始まり、よりリッチな顧客に関する情報源が登場した
  • 3: 契約前の顧客の情報源として、Salesforceが登場した

こういった状況に対し、例えば2の段階ではCRMに関するsat_user_crmを後から追加したり、3では異なるシステムで同じ実態であることを表わすために後からSame as Linkを導入して対処できます。

ビジネスは生き物なので、こうした途中から状況が変わることはよくありますし、現職の10Xでもまさにそういったことが現在進行形で起こっています。最初はネットスーパであるStailerを運営していましたが、AI発注やAIプライシングなど小売の経営課題を解決するために、ネットスーパーを単独で運用していたときには存在しなかったデータソースやビジネスプロセスが出現しています。こうした状況に対して、Data Vaultはアジリティを高く保ちながら、後から拡張できることはビジネスを支える上でも技術的な強みの一つになっています。

AIやLLM Agentとの相性がよい

Data Vaultを導入した際、これほどAIが発達してくるとは想像もできませんでした。しかし、結果的に、AIやLLM Agentとの相性がよいというのはData Vaultの大きな利点の一つだなと感じます。もう少し詳しく説明しましょう。

前述した通り、Data Vaultはディメンショナルモデリングの前段に配置しています。特にHubやLinkの定義はAnalytics Engineerが頭を使うべきところではありますが、そのモデリングが終わればAutomateDVを使った記述はかなり機械的になります。そのため、こうした箇所の記述はLLM Agentが書いてくれることが多く、相性がよいです。

また、Raw Vault / Business Vaultまでの実装が終わっていれば、コンポーネントが揃っているため、Factテーブル / Dimensionテーブルあるいはデータマートを組み立てるのはパズルを組み立てるのに近い感覚でできることも多いです。JOINの回数は多くなることが多いですが、そういったところはLLM Agentがかなり得意なところであり、Analytics Engineerはより基礎となるRaw VaultやBusiness Vaultの設計に時間を回せます。

レイヤリングもLLM Agentたちに仕事をさせる上で武器となります。「Factテーブル / Dimensionテーブルあるいはデータマートを組み立てる際、よく設計されたRaw VaultあるいはBusiness Vaultのみを使うように」といった制約をCI/CDで表現できます。こうした制約はLLM Agentが勝手にBusiness Vault以外でビジネスロジックを新しく作り出さないように、といったSSoTを守るためのガードレールの役割にもなっており、結果としてこの時代に非常に適合したデータモデリングおよびフレームワークとして機能します。

また、LLM Agent自身がData Vaultのことをよく知っているのも強みになります。最近登場したフレームワークはLLMの知識のカットオフ以前でないと情報があまりない、ということが発生しがちです。しかし、Data Vaultは初版がリリースされたのが2001年、2.0の発表や教科書の出版が2013年や2015年と20年近くの歴史があり、LLM自身がData Vaultに非常に詳しいです。枯れたデータモデリングを利用しながら、LLM Agentのような新しい武器も併用できるのは非常に心強いと思います。

履歴が使いやすい

Data Vaultを運用していると、履歴を自然と扱えるのは嬉しいポイントの一つです。データ分析をする上だと現在値のみでは不十分なケースも当然あります。Data Vaultで履歴と言えばSatelliteが思い浮かぶ人も多いと思いますが、HubとHubの紐付けの履歴を表現するLinkも履歴を表現しています。

当然ある時点でのユーザーと店舗の紐付けだけでも有用ですが、例えばユーザーと店舗のLinkで考えてみると

  • ユーザーは引越しなどを起因に利用する店舗を変えることがある
  • 販促の効果を測定(ファネル分析)するなどの文脈で、商品を購入した時の店舗ではなく、アプリをインストールした直後に紐付けた店舗の情報が欲しいときがある
  • この場合はユーザーと店舗の紐付け(Link)の中で最古の情報が欲しい

といったケースで履歴が役に立ちます。

紐付けがなくなった、といったことを表現したい場合もあると思います。Data VaultはRecord Tracking Satelliteと通じて表現するパターンがあるので、特に困らずに履歴を任せることができます。


dbtで履歴と言えば、SCD Type2の形式で履歴が保存できるdbt snapshotを使っている人も多いと思います。もちろん、これはこれで便利ですが、Data Vaultが保持している履歴はデータモデリングをした後(HubやLinkとして表現した後)のデータのため、取り回しがしやすいのも利点だなと思います。

運用する中で工夫が必要な点

ここまではデータ分析基盤でData Vaultを運用してよかった点を語りましたが、当然運用していると難しかった点や工夫が必要な点もありました。そこについても触れていきます。

データソースのスキーマに引っぱられないようにする

前述したようにData Vaultの肝の一つにHubやLinkといったデータモデリングの存在があります。一方で、Data Vaultでのモデリングに慣れていない人は「スキーマがこうなっているから…」とデータソースに引っぱられてデータモデリングをしてしまうケースがありました。例えば、以下のようなケースです。

  • データソース側のスキーマのモデリングがそもそもよくない場合がある
    • データソース側の生成者も把握はしているが、歴史的経緯などで修正が難しい場合がある
    • データ分析側のデータモデリングもそれに引っぱられるのはよくない
    • データ分析で使いやすい形にモデリングしなおすのがよい
      • 難しい場合ももちろんあり得るので、その場合はデータソースの生成者とコミュニケーションを取りに行くのも重要
  • データソース側が非正規化されている場合がある
    • RDBMSではないデータソース(例: Firestore)を利用している場合、非正規化された状態でレコードが入っている場合があります
      • 業務システムとしてのパフォーマンスを出すため、など様々な理由が考えられます
    • この状態のテーブルをData Vaultにマッピングしようとすると、うまく行かないことが多いです

こういったケースは、データソースのテーブルを適度に正規化した上で、HubやLinkは何か(データ分析で必要なEntityやビジネスプロセスは何か)を見極めていく必要があります。簡単ではない場合もありますが、ここで手を抜いてしまうと後段のディメンショナルモデリングやデータマートへの影響が大きく出てしまうため、踏んばって頑張る価値があります。

すでに運用しているデータ分析基盤との付き合い方

「Data Vaultは興味あるけど、すでに運用しているデータ基盤もあるし、フルスクラッチで作り直すわけにもいかないからなぁ…」という形でData Vaultに手を出せない、という方もいると思います。我々のケースもこれと同じで、すでに既存のデータ基盤が運用されており、多数のデータマートやadhocなテーブルが存在していました。

しかし、こうした状況はある意味チャンスでもあります。すでにデータマートが存在する場合、その中にEntityやビジネスプロセスが表現されていることも多いためです。それらをData Vaultの表現(Hub / Link / Satellite)として表現していきましょう。関連するマートが存在する状態であれば、HubやLinkの設計がよいものになっているかを当てはめながら考えることができます。そうして作ったコンポーネントを元にデータマートをリファクタリング(データ活用者へのインファーフェイスは変えず、内部のロジックをData Vaultへ置き換えてデータ品質を担保しやすくする)していけば、徐々にData Vaultへの移行が可能です。データマートに散らばったビジネスロジックをBusiness Vaultに集約すればSSoTの担保もしやすく、新規にデータマートを作る時にも心強い存在になります。

もちろん、全てのテーブルをData Vaultで作る必要はありません。本当に短命でデータ品質が保証されている必要がないものであればData Vaultを経由する必要もないでしょう。重要なのはデータの活用者側との期待値との乖離がないか、です。10Xではデータに対する期待値をData Reliability Levelとして定義をしており、例えば「TrustedのレベルのデータはData Vaultを通じて適切にデータモデリングされたもののみを使って構築する」などの濃淡を付けています。

Data Vaultでデータモデリングすべき対象が多く、どういった優先順位でやるべきかを迷うケースもあると思います。こういった優先順位の付け方はアジャイルデータモデリングなどでプラクティスが紹介されており、それを参考にできます。具体的には

  • イベントマトリックスやディメンショナルマトリックスを作る
  • それらを元に重要度を計算する
    • 重要度の計算方法は書籍の4.3節を参照してください
  • 重要と判断されたディメンション / イベントに紐付くHubやLinkを優先的に実装する

といった具合です。

Data Vaultの設計をできるメンバーをチーム内でどう増やすか

「データモデリングやHub / Link / Satelliteの設計が重要なのは分かった。じゃあ、それをできる人をチーム内にどうやって増やすか?」は悩ましい問題です。「銀の弾丸は存在しないので、地道にやっていきましょう」は実りがあまりに少ないので、チーム内で試行錯誤したことを書いておきます*2

まず、ペアプロというか、ペアモデリングの時間を意図的に増やしました。いきなりペアでモデリングをやる場合もあれば、叩き台となるモデリングを作ってきてもらって同期的にレビューや改善をしていくなど、やり方は様々です。この会のよいところは色々なモデリングのパターンを考慮でき、それぞれのメリット / デメリットを考慮した上でなぜ最終的な決定まで至ったかを共有できるところにあると思います。10Xではwhimsicalのような可視化ツールを使っていますが、MiroでもMarkdownにMermaid記法でも何でもよいと思います。

その後は設計したモデリングを実際にPull Requestでコードに落とし込みます。一気に作ると、コード量が多くなったり、議論が収束しない場合もあるので

  • ステップ1: raw_stage / stage / raw_vault / business vaultを作成
    • business vaultのビジネスロジックが混み合う場合にはPull Requestを分割する場合もあります
    • その場合はdbtのUnit testsを実装することも増えてきました
      • どういったテストをすべきかもどこかにまとめたいですが、それはまた別の機会に…
  • ステップ2: fact / dimension / martを作成

といったステップに分割することが多いです。

ステップでいうと、ステップ1が論点が多くなりがちで、ステップ1がうまくいっているとステップ2もうまくいく、というパターンが多いです。「データモデリングに慣れていない人はステップ2を先に経験してもらって」というのは、割と気を付けるべきパターンかなと思っています。ステップ2は出来上がったパーツを組み立てる形になることが多く、データモデリングに関する経験値があまり貯まらないことが多いです。時間がかかるかもしれませんが、ステップ1から頑張ってやってもらう、というのが私はオススメかなと思っています。

Data Vaultをベースにしたデータ基盤がある程度枯れてくると、新規にデータモデリングをする機会が実はそれほど頻繁にない(=データモデリングの学習の機会が少ない)という課題にぶち当たることもあります。こうした場合、過去の実装時の議論や論点があったPull Requestを観光名所として案内する勉強会をチーム内で開催しました。下記のエントリはディメンショナルモデリングのタイトルにしていますが、実際にはData Vaultに関する内容もかなり議論しています。

full-refreshとどう向き合うか

Data Vault自体は前述したように履歴を扱えるフレームワークになっていますが、稀に履歴を消した上で作り直さないといけない場面(dbtでいうところのfull-refreshしないといけない場面)に向き合う必要があったので、それについても書いておきます。

これは10Xのデータ基盤のデータソースの都合がどうなっているかも関わってくるため、簡単に整理します。大きく分けると、以下の2種類のデータソースが存在します。

  • 1: Firestore
    • 我々の中心となるデータベースで、顧客 / 店舗 / 注文 / … / etcに関する情報が保存されています
    • 毎日Firestoreの現在の状態をGCSに書き出し、BigQueryに取り込むバッチが動いています
      • CDCのような仕組みではなく、洗い替えに近い形で取り込んでいます
    • データ分析基盤では、BigQueryに取り込まれたテーブルを参照しています
  • 2: Google Analytics for Firebase
    • Firebaseを利用している場合に使えるGoogle Analyticsと思ってほぼ問題ありません
    • 「カートに商品を追加した」「通知の画面をタップした」などのイベントに起因するレコードを保存します
    • イベント形式のデータがほぼDailyでBigQueryにexportできるため、それを利用しています
    • 基本的にはappend-onlyな形でデータが書き込まれます*3

1と2のData Vaultへの取り込みは毎朝のdbtのバッチで行なっており、Data Vaultに対するload_timestampはdbtの実行時間を指定していました(具体的にはload_timestamp: "timestamp('{{ run_started_at }}')")。

こうした前提の元で、それぞれのケースで以下のような履歴の再構成を迫られる場面が稀にありました。

  • 1: Firestore
    • Hubの設計がよくなく、ハッシュキーを構成するのに必要なビジネスキーを考慮できていなかったケース
  • 2: Google Analytics for Firebase
    • テスト用のユーザーが入ってしまっており、取り除きたいケース
      • 後段で毎回同じようなフィルタ処理を書きたくない
    • 特定のイベントを取り込み忘れていて、後から追加したいケース

こういったケースでなぜfull-refreshを伴なう履歴の再構成が必要になったかですが、以下のようなクエリが問題になったからです。t_link_eventload_timestampsat_usersat_shopload_timestampで挟み込むようなケースです。

from t_link_event
left outer join
    sat_user
    on t_link_event.user_hk = sat_user.user_hk
    and t_link_event.load_timestamp >= sat_user.load_timestamp
    and t_link_event.load_timestamp < sat_user.next_load_timestamp
left outer join
    sat_shop
    on t_link_event.shop_hk = sat_shop.shop_hk
    and t_link_event.load_timestamp >= sat_shop.load_timestamp
    and t_link_event.load_timestamp < sat_shop.next_load_timestamp

このクエリは「FirestoreもGoogle Analytics for Firebaseも毎朝のdbtのバッチで取り込まれ、Data Vaultに対するload_timestampはdbtの実行時間が指定されている」という前提が満たされて初めて成立するクエリです。そのため、t_link_eventのみ再構成やsat_usersat_shopのみ再構成では前提が満たされず、クエリ結果は空になりこれは意図した結果ではありません。

特にFirebaseの場合、現在時点のスナップショットしか存在しないため、full-refreshを実行すると、過去時点の履歴はもう二度と手に入れることができません。


こうしたケースへの対応策はいくつか考えられます。

まず一つ目は設計漏れや取り込み漏れがないようにレビューや仕組み作りを頑張るといったものです。特にGoogle Analytics for Firebaseで独自の新しいイベントが作られた場合、Data Vaultへの取り込み忘れが起きやすいですが、Data Contractの仕組みを経由して取り込み忘れがそもそも起きにくいようにする、といった取り組みを行なっています。

二つ目がload_timestampだけではなくeffective_from(例: t_linkの場合)も活用する、です。特にGoogle Analytics for Firebaseでは、dbtを実行した時刻(run_started_at)ではなくイベントが発生した時刻(event_timestamp)をeffective_fromとして活用すれば上記の問題に対応しやすくなります。

三つ目は「Data Vaultの前段で履歴を取る」です。これは特にデータベース側であるFirestoreのような存在に取って重要です。前段で履歴を取るやり方は以下のように複数あります。

  • 1: CDCのようなRDB側の仕組みで全ての変更差分を受け取る
  • 2: dbt snapshotのような仕組みで差分をBigQuery側に溜め込む
  • 3: データのスナップショットや差分をGCSで受け取り、それらをBigQueryに取り込む

どのやり方でもよいですが、この仕組みを整えれば「現在時刻ではなく、取り込み日時を外から差し込んでやり直す」ということが可能になります。AutomateDVのStaging層では例えば以下のように記述できます。このやり方であれば、full-refreshをしたとしても完全に履歴を再構成できます*4

{%- set yaml_metadata -%}
...
derived_columns:
  ...
  load_timestamp: "timestamp('{{ var('target_date') }}', 'Asia/Tokyo')"
  effective_from: "timestamp('{{ var('target_date') }}')"

このように

  • full-refreshをそもそもしないでよい仕組み作りを整備する
  • full-refreshが必要になった場合でも履歴を再構成できるデータの持ち方をする

といった工夫をすることで運用に耐えられるようにしています。

ハッシュキーの構成の順序問題

これは他の問題と比べると大分具体的な問題ですが、ミスると致命的になり得るので書いておきます。AutomateDVのフレームワークでは、Staging層でハッシュ化したビジネスキーをサロゲートキー(_hkのサフィックスが付くキー)として定義します。例えば、以下のように定義します。

{%- set yaml_metadata -%}
source_model: "raw_stg_order_transaction_product"
derived_columns:
  record_source: "!my_db"
  ...
hashed_columns:
  order_hk:
     - "order_id"
  transaction_product_hk:
    - "order_id"
    - "transaction_product_id"
  order_transaction_product_hk:
    - "order_id"
    - "user_id"
    - "transaction_product_id"

ハッシュキーはビジネスキーの配列を元に構成されます。配列に渡す順番は重要で同じ名前のハッシュキーであっても["A", "B", "C"]["A", "C", "B"]では生成されるハッシュキーの値は異なります。これはハッシュキーの生成のアルゴリズム上仕方のないことではありますが、渡す順番が異なったためにJOINで紐付けられないことが起きると非常に困ります。リポジトリ内でハッシュキーの構成の仕方は一貫性があるべきです。

これを防ぐため、以下のスクリプトをCI/CDで回すことによってファイルによって同じハッシュキーであるにも関わらず構成の仕方が異なる、ということがないように担保しています。

ハッシュキーの構成の仕方を担保するスクリプト(クリックで開きます)
import logging
import os
import re
import sys
from dataclasses import dataclass
from typing import Any

import yaml





@dataclass
class HashedColumnDefinition:
    name: str
    filename: str
    keys: list[str]


def get_sql_files(target_dir: str) -> list[str]:
    sql_files = []
    for root, _, files in os.walk(target_dir):
        for file in files:
            if file.endswith(".sql"):
                sql_files.append(os.path.join(root, file))
    return sql_files


def validate_hashed_column_definitions(
    hash_key: str, definitions: list[HashedColumnDefinition]
) -> None:
    if len({"/".join(definition.keys) for definition in definitions}) > 1:
        logging.error(
            f"ハッシュキー(={hash_key})の定義がファイルによって異なります。JOINでレコードが紐付かない原因になるので、定義を揃えてください。"
        )
        for definition in definitions:
            logging.error(f"{definition.filename}での定義:")
            for k in definition.keys:
                logging.error(f"- {k}")
        sys.exit(1)


def extract_and_load_yaml(filename: str) -> dict[str, Any]:
    with open(filename, "r") as file:
        text = file.read()

    match = re.search(
        r"\{%- set yaml_metadata -%\}\n(.*?)\n\{%- endset -%\}", text, re.DOTALL
    )
    if match:
        return yaml.safe_load(match.group(1))
    else:
        raise ValueError(f"YAML metadata not found in {filename}")


if __name__ == "__main__":
    logging.basicConfig(
        level=logging.INFO,
        format="%(levelname)s: %(message)s",
    )

    stage_dir = "models/stage"
    sql_files = get_sql_files(stage_dir)

    hash_key_by_hashed_column_definitions = {}
    for file in sql_files:
        try:
            yaml_dict = extract_and_load_yaml(file)
            for hk, keys in yaml_dict["hashed_columns"].items():
                hash_key_by_hashed_column_definitions.setdefault(hk, []).append(
                    HashedColumnDefinition(name=hk, filename=file, keys=keys)
                )
        except ValueError as err:
            logging.info(f"Skipping {file} because {err}")

    for hk, definitions in hash_key_by_hashed_column_definitions.items():
        validate_hashed_column_definitions(hk, definitions)

Data Vaultを採用すべきだったか?

事業の特性やデータ基盤のフェイズによって、ここの回答は変わってくるでしょう。10Xの場合、以下のような事業の特性がありました。

  • ネットスーパーだけでなく、AI発注や価格最適化などまさに多方面に事業が拡大している
    • Agileなデータウェアハウスの構築が可能であることが求められる
    • 複数のデータソースに対応することが簡単に行えることが求められる
  • 基本的に単一事業である
    • 出口となるデータプロダクトは複数登場しているが、それぞれのデータは横断して利活用したい場面が多い
    • 小売事業という単一の事業である
  • 対象にしているのが小売業界であり、エンタープライズ企業である
    • Data Vaultがまさに想定しているような業界

前述したようにData Vaultを運用する上での工夫は必要にはなってきますが、事業の特性を考えるとData Vaultを採用して得られたメリットはかなり大きいと感じています。

逆に言うと、以下のようなケースではData Vaultの採用は見送ったほうがよいかもしれません。

  • 複数の事業やサービスを展開しており、それぞれの事業やサービスの独立性が高い
  • 少人数でデータ基盤を運用しており、特にデータモデリングに関するケイパビリティが高くない
    • 例: アナリストが兼任に近い形でデータマートを運用しているケース

…とはいえ、運用にData Vaultを採用しなくてもデータエンジニアリングに長く関わる人であればData Vaultを学習してみると得られるものは多いと思います。mashiikeさんのポストが印象に残っているので、貼っておきます。

宣伝: データエンジニアの採用やってます

これまでに書いたようにData Vaultをゴリゴリと活用 / 運用しながらデータ基盤 / データプロダクトの開発を行なっています。最近だと特に新規事業でのデータエンジニアの採用もやっているので、気になった方は是非カジュアル面談でお話しましょう。





Source link

関連記事

コメント

この記事へのコメントはありません。