Data Vaultにおけるリンクの考察:トランザクションを表現するリンクのモデリング
Data Vaultにおけるトランザクション(取引・イベント)を表現するリンクの設計パターン、粒度の課題、そして「Keyed-Instance Hub」を用いたスマートな解決策を解説。
著者:Petr Beles(2150 GmbH / Datavault Builder)
公開日:2017-11-22
Data Vaultにおいてトランザクションを表現するリンク
本記事では、トランザクション(取引やイベント)を表現するリンクのみに焦点を当てて議論します。マスターデータ間の関係性(例:都市と自治体の関係など、オブジェクトにコンテキストを与える関係リンク)は意図的に除外しています。
Data Vaultにおける従来のリンクの1行は、関連するすべてのハブの複合ビジネスキー(BK)のハッシュ値、および必要に応じた依存子キー(Dependent Child Key)の組み合わせによって一意に定義されます(『Building a scalable DWH with DV 2.0』 Linstedt & Olschimke 4.4.5)。
| 受注UID | 受注番号(Sales Order ID) | 明細番号(Line No) | 顧客名 | 製品名 | 数量 | 単価 | 合計金額 |
|---|---|---|---|---|---|---|---|
| 345789 | SO-02155 | 1 | Smith | ワイン(ボトル) | 6 | $20 | $120 |
従来のリンク設計では、上記の関係性すべてがリンク行の識別に用いられ、数量や金額などの属性はリンクサテライトに格納されます。
現実世界において、トランザクションの多くは独自の業務キー(技術キーではなく、顧客に提示される番号)を持っています。例えば注文(受注)には注文番号があり、各明細行には行番号(明細番号)が存在します。行番号単体では弱エンティティ(Weak Entity)であっても、「注文ID+明細行番号」を組み合わせることで完全に一意に識別可能です。
このことは、リンク行のハッシュキーが「注文トランザクション(明示的にモデル化されている場合)」や「明細行番号(依存子キー)」といった識別関係だけでなく、「顧客」や「製品」といった非識別関係(相手先エンティティ)の値にも依存して生成されてしまうことを意味します。
言い換えれば、従来のリンクハッシュキーは、事実上「トランザクション識別子」ではなく**「関係性の組み合わせを表す HashDiff キー」**として機能しているのです。
トランザクションがVaultに投入された後、顧客や製品といった関係先が一切変更されないのであれば、リンクの粒度はトランザクションの粒度と一致するため問題ありません。
しかし製造業や流通業など多くの業界では、トランザクション投入後に「顧客の付け替え」や「代替製品への変更」といった業務変更が日常茶飯事として発生します。また、コールセンターでの暫定登録後に正式顧客コードを後から紐付けるといった「遅延到着リレーション(Late-arriving relations)」も珍しくありません。
従来の標準リンク設計では、顧客や製品が変わるたびに新しいハッシュキーが生成され、新たな行がリンクテーブルに追加されます。技術的には書き込み性能に優れ、すべての変化を追跡できる実証済みの設計ですが、以下の深刻な課題が生じます。
なぜ従来の設計を見直すべきなのか?
- テーブル粒度(Grain)の曖昧さ:
同一の注文明細に対して、顧客変更に伴いリンクテーブル内に2行が存在することになります。さらに顧客が元に戻った場合でも2行のままです。トラッキングサテライトを正しくクエリすれば履歴は復元できますが、レポート設計者に対して「このリンクテーブルの粒度は何であるか」を論理的に説明することは極めて困難です。 - リンク間リンク(Link-to-Link)の禁止原則との衝突:
トランザクション同士を紐付ける際、リンク同士を直接結合することはアジリティや依存関係の観点から禁止されています。トランザクション自体ではなく、特定の「トランザクションのバージョン」同士を結びつけてしまうリスクが生じます。
状況を劇的に改善する提案:Keyed-Instance Hubアプローチ
筆者が提唱し、実運用で大きな成果を上げている改善パターンは以下の通りです:
- トランザクションの最細粒度を表すハブ(Keyed-Instance Hub)を必ず作成する。
上記の例であれば、「注文ID+明細行番号」の複合BKを持つ「受注明細ハブ(Hub Sales Order Line)」を作成します。 - リンクから依存子キーを排除し、この最細粒度ハブへのリンクを含める。
- トランザクションのコンテキスト属性(数量、単価、合計等)は、リンクサテライトではなく、このハブに通常のサテライトとして直接接続する。
- ソース側での削除を検知するトラッキングサテライトも、リンクではなくこのハブに接続する。
- トランザクションが事後変更され得るかどうかに応じてリンクをロードする:
- 変更があり得る場合:最細粒度ハブをドライビングハブとして、リンクを SCD Type 2 スタイルでロードする。
- 変更がない場合:SCD Type 0 スタイル(初回登録のみ)でロードする。
- 2つのトランザクションを関連付ける場合は、それぞれの最細粒度ハブ同士の間にリンクを作成する。
テーブル構造のイメージ
ハブ:Hub Sales Order Line
| Sales Order ID | Order Line Number | Sales Order Line Hash (HK) |
|---|---|---|
| SO-02155 | 1 | 85036a318c424cdd… |
サテライト:Sat Sales Order Line
| Load Time | Sales Order Line Hash | 数量 | 単価 | 合計金額 |
|---|---|---|---|---|
| 2012-11-02 11:22 | 85036a318c424cdd… | 6 | $20 | $120 |
リンク:Link Sales Order Line(SCD Type 2スタイル)
| Load Time | Sales Order Line Hash | Product Hash | Customer Hash |
|---|---|---|---|
| 2012-11-02 11:22 | 85036a318c424cdd… | 2bdf8a1484a74e1d… | 1e292c434a272b21… (Smith) |
| 2012-11-04 11:22 | 85036a318c424cdd… | 2bdf8a1484a74e1d… | 9f8a32bc41de76a2… (Miller) |
この設計により、リンクの主キーは「ドライビングハブのハッシュキー+ロード日時」となり、通常のサテライトと全く同じ決定論的パターンでロードおよびクエリが可能になります。
最新状態を取得したい場合も、ドライビングハブキーで PARTITION BY して最新のロード日時を抽出するだけで完了し、多段テーブル結合が一切不要になります。何よりも、「トランザクションの各バージョンが時系列で1行に対応する」 という極めて明確で直感的な粒度が回復します。
また、後から追加された任意の店舗コードなどのオプショナルな関係性も、別個の疎なリンクテーブルとして追加し、同一ハブキーでロスレスに結合できます。
これはData Vault標準に反しているか?
大部分において完全に準拠しています:
- トランザクションの最細粒度でハブを作成すること(Keyed-Instance Hub)は何ら制約されません(Genesee Academyでも提唱されています)。
- ハブキーに直接サテライトをぶら下げることも標準の範疇です。
- 保存されたデータから元のすべてのソースデータが完全に復元可能(ロスレス性)です。
唯一の違いは、リンクテーブルにおいて同一トランザクションの変更履歴が時系列行(SCD Type 2)として保持される点です。しかし、これによりクエリは劇的に単純化され、実務上の保守性は飛躍的に向上します。
結論
このパターンを採用するかどうかは自由ですが、筆者たちはこのパターンを用いてリアルタイム処理を含む数多くの大規模エンタープライズ現場でその堅牢性を実証してきました。
トランザクションを表現するリンクにおいて粒度の混乱や後続レイヤーのクエリ複雑化に直面しているなら、このKeyed-Instance Hubを活用したリンクモデリングを強く推奨します。