経営会議の資料で、「取引先の数」が部署ごとに違っていました。営業のCRMでは12,400社、請求の基幹システムでは9,800社、ECサイトには別の会員IDがあります。データ担当のあなたは、半年かけて顧客の統合マスタを作りました。ところが翌期に営業の組織が変わり、顧客の担当区分が組み替えられます。ディメンションの表を作り直し、過去分を再計算すると、経営企画から「先月の資料と数字が違う。どちらが正しいのか」と聞かれました。業務が変わるたびに作り直しが降ってきて、そのたびに過去の数字まで動きます。
この「業務が変わるたびに統合の表を作り直す」状態から抜けるためのモデリング手法が、Data Vault 2.0です。業務の概念(顧客、商品、注文)と概念どうしの関係、属性の履歴を別々の表に持ち、項目や組織が変わっても既存の表は壊さずに表を足して対応します。提唱者はDan Linstedtで、Michael Olschimkeとの共著『Building a Scalable Data Warehouse with Data Vault 2.0』(Morgan Kaufmann、2015年)に体系がまとまっています。
Data Vaultが効く会社、効かない会社を4つの問いで分ける
表の数もJOINの数も増える手法なので、痛みがない会社が入れると手間だけが増えます。次の4つの問いに答えてみてください。
| 問い | 「はい」ならData Vaultの出番 | 社内での確かめ方 |
|---|---|---|
| 同じ顧客や商品を、2つ以上のシステムが別々のIDで持っているか | 統合の軸(Hub)を作る価値がある | CRM・基幹・ECのそれぞれで、顧客の件数とIDの体系を並べる |
| 業務側の項目や区分の変更が、半年に1回以上あるか | 「表を足すだけ」の構造が効く | 過去1年のデータ基盤への改修依頼を数え、属性追加や区分変更が何件あったかを見る |
| 過去の時点の値を、後から正確に再現する必要があるか(監査、規制対応、月次の締め直しなど) | 履歴を標準で持てることが効く | 経理や監査の担当に、過去の時点の数字を求められた回数を聞く |
| 設計を学ぶ期間(目安として1〜2か月)を、データエンジニアが取れるか | 学習の負担を吸収できる | 担当者の本業の工数を、上長と一緒に確かめる |
3つ以上が「はい」なら、統合層をData Vaultで作る価値があります。1〜2つなら、スタースキーマに履歴の仕組み(SCD Type 2)を足すほうが早く結果が出ます。0なら過剰です。4つ目だけが「いいえ」なら、対象を1業務に絞って学習の負担を小さくします。
3種類の表に分けて持つ
データは3種類の表に分けて持ちます。役割を混ぜないことが、変更に強い理由そのものです。
| 表の種類 | 持つもの | 持たないもの | 例(小売) |
|---|---|---|---|
| Hub | 業務の概念を識別するキー(ビジネスキー)だけ | 属性 | HUB_CUSTOMER(顧客番号)、HUB_PRODUCT(商品コード) |
| Link | Hubどうしの関係だけ | 属性 | LINK_ORDER_LINE(注文×商品)、LINK_ORDER_CUSTOMER(注文×顧客) |
| Satellite | HubやLinkにひもづく属性と、その変化の履歴 | キーの定義 | SAT_CUSTOMER_PROFILE(氏名・住所の変遷)、SAT_PRODUCT_PRICE(価格の変遷) |
冒頭の場面に当てはめると、「顧客の担当区分」はSatelliteに入ります。区分が組み替わっても、Satelliteに新しい行が足されるだけです。顧客のHubも、過去の区分の行も残ります。「先月の資料の時点ではこの区分だった」という数字を、後からそのまま再現できます。
項目が1つ増えたとき、作業がどう変わるか
顧客に「業種区分」という項目が増えた場面で、作業を比べます。
| 作業 | スタースキーマで作っている場合 | Data Vaultで作っている場合 |
|---|---|---|
| 表の変更 | 顧客ディメンションに列を足す | 業種区分のSatelliteを1つ足す。既存の表は触らない |
| 過去の行の扱い | 過去の行の値をどうするか(空にするか、遡って埋めるか)を決める | 新しいSatelliteは、取り込んだ時点から履歴が始まる |
| 影響の確認 | このディメンションを使う集計表(マート)をすべて確かめる | このSatelliteを使うマートだけを足す |
| 過去の数字 | 作り直し方によっては、過去の集計値が動く | 既存のSatelliteは変わらないので、過去の数字は動かない |
1つのHubにSatelliteは複数持てるので、ソースシステム・更新頻度・機密度ごとに分けられます。公開してよい属性と本人確認データを別のSatelliteにすれば、アクセス権も別々にかけられます。
統合層はData Vault、分析用の表はスタースキーマの二段で組む
JOINが多いぶん、BIから直接読むと重くなります。統合と履歴はData Vaultで持ち、BIが読む表は別にスタースキーマや横長の表で用意します。データを層に分けて品質を上げていく3層の考え方(メダリオンアーキテクチャ)に当てはめると、次の分担になります。
| 層 | 置くもの | Data Vaultの役割 |
|---|---|---|
| Bronze | ソースの生データ(ソースの構造のまま) | 使わない |
| Silver | クレンジングと統合 | Hub・Link・Satelliteで組む |
| Gold | 用途別の集計表(マート) | 使わない。Silverから切り出したスタースキーマや横長の表を置く |
Gold層では、Data Vaultの履歴を「最新の値だけ」「過去すべて」「特定の時点」と用途に合わせて切り出します。BIの利用者からは、見慣れたスタースキーマが並んでいるようにしか見えません。層の分け方はメダリオンアーキテクチャ(Bronze/Silver/Gold)の解説に、dbtで層を組む方法はdbtの3層構造に書いています。
小さなECサイトで、モデリングを5つの手順で進める
会員登録した顧客が、商品コードで管理された商品を、1回の注文で複数買うECサイトを題材にします。配送先は注文ごとに指定でき、自宅以外も選べます。
手順1:業務の名詞を洗い出し、Hubにするかを決める
| Hubの候補 | ビジネスキー | 判断 |
|---|---|---|
| 顧客 | 顧客ID | Hubにする。業務の中で独立して存在する |
| 商品 | 商品コード | Hubにする |
| 注文 | 注文番号 | Hubにする |
| 配送先住所 | 住所ID | 迷う。複数の注文で同じ住所を参照する業務なら独立したHub、注文ごとに入力して使い回さないならSatellite |
迷ったら、最初はSatelliteで作ります。使い回す場面が増えてからHubに昇格させるほうが、最初からHubを増やしすぎるより手戻りが少なくて済みます。
手順2:Hubどうしの関係をLinkで結ぶ
「注文×商品」の明細と「注文×顧客」をLinkにします。明細の数量や単価は、Linkには直接書きません。Linkにひもづく別のSatelliteに持たせます。
手順3:属性を、変わる頻度と機密度でSatelliteに分ける
| Satellite | 持つ属性 | 分けた理由 |
|---|---|---|
| SAT_CUSTOMER_PROFILE | 氏名、メールアドレス | 基本情報で、変更は少ない |
| SAT_CUSTOMER_PREFERENCES | メールマガジンの受信設定など | 頻繁に変わる |
| SAT_CUSTOMER_SENSITIVE | 本人確認のデータなど | アクセス権を分けたい |
手順4:ハッシュキーで表をつなぎ、差分だけを足す
2.0では、ビジネスキーをハッシュ関数にかけた値を、表どうしをつなぐキーにします。連番を採番する方式と違い、各表が他の表を待たずにキーを作れるので、並列にロードできます。
いちばん事故が多いのは、ハッシュにかける前の正規化の漏れです。同じ顧客IDが「ABC123」「abc123」「 ABC123 」と混ざると、別の顧客として扱われます。大文字への統一、前後の空白の除去、NULLの置き換えを、入口で必ずそろえます。Snowflakeの例です。
-- ビジネスキーを正規化してからハッシュにする
SELECT
MD5_BINARY(UPPER(TRIM(COALESCE(customer_id, '')))) AS hub_customer_hk,
customer_id AS customer_bk
FROM stg_customer;
SnowflakeのMD5_BINARYは16バイトの値を返します。公式ドキュメントは、MD5は暗号の用途にはもう使うべきでないが、破損の検出のような用途には使えるとしています。キーは識別のためのもので、暗号の強さは要りません。社内規程でMD5を禁じている場合は、SHA2_BINARYなどに替えます。
Satelliteは、属性をまとめてハッシュにした値(hash_diff)を前回の最新行と比べ、違うときだけ新しい行を足します。
WITH source AS (
SELECT
MD5_BINARY(UPPER(TRIM(COALESCE(customer_id, '')))) AS hub_customer_hk,
MD5_BINARY(CONCAT_WS('|',
COALESCE(customer_name, ''),
COALESCE(address, ''),
COALESCE(email, '')
)) AS hash_diff,
customer_name, address, email,
CURRENT_TIMESTAMP() AS load_date,
'ec_main' AS record_source
FROM stg_customer
),
latest AS (
SELECT hub_customer_hk, hash_diff
FROM sat_customer_profile
QUALIFY ROW_NUMBER() OVER (PARTITION BY hub_customer_hk ORDER BY load_date DESC) = 1
)
SELECT s.*
FROM source s
LEFT JOIN latest l USING (hub_customer_hk)
WHERE l.hash_diff IS NULL OR l.hash_diff != s.hash_diff;
連結する列の順番を変えると、値が同じでもハッシュが変わります。列の順番は設計のときに決めて、変えないルールにします。
dbtで書く場合は、HubやSatelliteの定型を関数のように呼べるパッケージのAutomateDV(旧名dbtvault)が使えます。公式ドキュメントでは、Snowflake・BigQuery・SQL Server・Databricks・Postgresに対応しています。Hubは次のように書けます。
-- models/silver/hub_customer.sql
{{ config(materialized='incremental') }}
{{ automate_dv.hub(
src_pk='hub_customer_hk',
src_nk='customer_bk',
src_ldts='load_date',
src_source='record_source',
source_model='stg_customer'
) }}
手順5:Gold層に、BIが読む表を別に作る
| マートの例 | 元にする表 | 用途 |
|---|---|---|
| FACT_DAILY_ORDER | HubとLinkとSatelliteを結合 | 日次の売上ダッシュボード |
| DIM_CUSTOMER_LATEST | SAT_CUSTOMER_PROFILEの最新行 | 顧客の今の属性を引く |
| DIM_CUSTOMER_ASOF | Satelliteの履歴を月末時点で切り出す | 「先月の資料の時点」の数字を再現する |
冒頭の「先月の資料と数字が違う」問いには、3行目の表で答えられます。
Snowflakeで増分ロードを自動化するときの注意
SnowflakeのStream(表の変更を追跡する仕組み)とTask(定期実行の仕組み)を組み合わせると、Bronzeに届いた新しい行だけをHubに足せます。
CREATE STREAM stream_bronze_customer_for_hub ON TABLE bronze.customer;
CREATE TASK task_load_hub_customer
WAREHOUSE = wh_vault_load
SCHEDULE = '15 MINUTES'
WHEN SYSTEM$STREAM_HAS_DATA('stream_bronze_customer_for_hub')
AS
INSERT INTO silver.hub_customer
SELECT DISTINCT
MD5_BINARY(UPPER(TRIM(COALESCE(s.customer_id, '')))),
s.customer_id,
CURRENT_TIMESTAMP(),
'ec_main'
FROM stream_bronze_customer_for_hub s
WHERE s.METADATA$ACTION = 'INSERT'
AND NOT EXISTS (
SELECT 1 FROM silver.hub_customer h
WHERE h.hub_customer_hk = MD5_BINARY(UPPER(TRIM(COALESCE(s.customer_id, ''))))
);
-- 作成直後のTaskは停止状態なので、動かすには再開が要る
ALTER TASK task_load_hub_customer RESUME;
詰まりやすい点を、公式ドキュメントの仕様と合わせて挙げます。
| 詰まる点 | 仕様 | 対策 |
|---|---|---|
| HubとSatelliteで同じStreamを読むと、片方に変更が届かない | Streamの読み取り位置は、DMLの中で使われたときに進む | 読み手ごとにStreamを分ける |
| Streamが使えなくなる | 元の表のデータ保持期間を過ぎるまで読まないと、Streamは古くなり再作成が要る | Taskを止めたまま長期間放置しない |
| Taskを作ったのに動かない | 作成直後のTaskは停止状態 | ALTER TASK … RESUMEで再開する |
| 長期の履歴をTime Travelで代用しようとする | Time Travelの既定の保持は1日、Enterprise Edition以上でも最大90日 | 長期の履歴はSatelliteで持つ |
dbtやオーケストレーションツールで変換を管理している会社なら、同じ流れをそちらのジョブで組みます。ツールの選び方はオーケストレーションツールの選び方で扱っています。
つまずきやすい設計の誤り
| 誤り | 何が起きるか | 直し方 |
|---|---|---|
| Hubのキーを連番で採番する | 並列ロードのたびに採番待ちが起きる | ビジネスキーのハッシュをキーにする |
| 明細の数量や単価をLinkに直接書く | 関係と属性が混ざり、属性の変更でLinkを触ることになる | Linkにひもづく別のSatelliteに分ける |
| 全属性を1つのSatelliteに詰める | 変更の多い属性につられて行が増え、機密の分離もできない | 変わる頻度と機密度で分ける |
| Gold層を作らず、BIから直接読ませる | JOINが多く、「Data Vaultは重い」と判断されて終わる | BI向けのマートを必ず別に作る |
既存のスタースキーマは残し、1つの業務から並走させる
動いているスタースキーマを全面的に作り替えると、移行のあいだ二重に管理することになります。変更が多く、履歴に困っている業務を1つ選び、既存の表の隣で並走させます。
| 月 | やること | 終わったと言える目安 |
|---|---|---|
| 1か月目 | 冒頭の4つの問いで対象業務を1つ選び、HubとLinkの候補を表にする | 業務担当者と、Hubにする名詞の一覧に合意している |
| 2か月目 | Hub・Link・Satelliteを作り、毎日ロードする | 既存の表と件数が一致している |
| 3か月目 | Gold層に、既存と同じ形のマートをData Vaultから作る | BIの数字が、既存のマートと一致している |
| 4か月目 | 業務側の変更を1件、Satelliteを足す形で取り込む | 既存の表を触らずに対応できた |
| 5か月目以降 | 次の業務に広げるかを決める | 変更1件あたりの作業時間が、以前より短くなっている |
5か月目の判断には、作業時間の比較を使えます。たとえば月給50万円のエンジニアなら、会社の総コストは給与の約3倍で年1,800万円、年1,900時間で割ると時給は約9,500円です。属性の変更1件にスタースキーマで40時間かかっていたものが8時間になれば、1件あたり約30万円の差になります。時間はどちらも説明のための例です。自社の改修記録で置き換えて計算してください。
よくある質問(FAQ)
スタースキーマにSCD Type 2を入れれば、Data Vaultは要らないのでは?
ソースが1つで、履歴を持ちたい属性が限られているなら、SCD Type 2で足ります。ソースが複数あって統合が要る場合や、属性の追加が頻繁な場合、機密度ごとに履歴を分けたい場合は、SCD Type 2では手作業が増えます。Satelliteで履歴の持ち方がそろっていれば、要件が増えても同じ手順で対応できます。
Data Vaultを入れると、BIの表示は遅くなりませんか?
BIから直接Data Vaultを読むと、JOINが多いので遅くなります。BIにはGold層のマートを読ませ、Data Vaultは統合と履歴の元として使います。Gold層への変換が重くなってきたら、ある時点のSatelliteの行をまとめたPIT(Point-in-Time)テーブルや、よく使う関係をまとめたBridgeテーブルを、計測したうえで足します。
dbtでData Vaultを実装できますか?
できます。AutomateDVのようなパッケージを使えば、Hub・Link・Satelliteの定型を呼び出す形で書けます。同じ形の表を何十本も並べる手法なので、定型を部品にしておく効果は大きく出ます。
ハッシュキーはBINARYと文字列のどちらで持つべきですか?
Snowflakeでは、MD5_BINARYが返す16バイトのBINARYのまま持つと、文字列で持つより容量が小さくなります。人が目で確かめる場面だけ、16進の文字列に変換して表示します。
最初から全部の業務をData Vaultにするべきですか?
最初から全部を移すのは避けます。変更が多く、過去の数字の再現に困っている業務を1つ選び、既存のスタースキーマの隣で並走させます。業務側の変更を1件、Satelliteを足すだけで取り込めたことを確かめてから、次の業務に広げます。
出典
- Elsevier: Building a Scalable Data Warehouse with Data Vault 2.0(Linstedt, Olschimke)(2026-10-17 参照:書名・著者・出版社(Morgan Kaufmann)・2015年刊)
- AutomateDV Documentation: Macros(2026-10-17 参照:hubマクロの引数(src_pk, src_nk, src_ldts, src_source, source_model)、対応DB(Snowflake・BigQuery・SQL Server・Databricks・Postgres))
- AutomateDV Changelog(2026-10-17 参照:v0.9.6(2023-05-16)の見出し「The rebrand update! – dbtvault is now AutomateDV」)
- Snowflake Documentation: MD5_BINARY(2026-10-17 参照:16バイトのBINARYを返す、暗号用途には使うべきでないが破損検出等には使える)
- Snowflake Documentation: Introduction to Streams(2026-10-17 参照:METADATA$ACTION、DMLで使われたときに読み取り位置が進む、保持期間を過ぎると古くなる)
- Snowflake Documentation: CREATE TASK(2026-10-17 参照:SCHEDULEの書式(MINUTES)、作成直後は停止状態、WHEN SYSTEM$STREAM_HAS_DATA)
- Snowflake Documentation: Understanding & using Time Travel(2026-10-17 参照:既定1日、Enterprise以上で最大90日)
「先月の資料と数字が違う」が組織変更のたびに起きているなら、どの業務からData Vaultで並走させるかの切り出しから一緒に考えます。初回相談(30分・無料)で一緒に整理できます。