From 5f16476c81a4e243aa4c798849c2a780b96b9691 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Sat, 15 Nov 2025 20:00:58 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D1=8B=D0=B9=20=D0=B2?= =?UTF-8?q?=D0=B0=D1=80=D0=B8=D0=B0=D0=BD=D1=82=20=D1=81=D1=82=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=D0=B8=20=D0=BF=D1=80=D0=BE=20DV?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dwh-modeling/DataVault.md | 552 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 552 insertions(+) create mode 100644 dwh-modeling/DataVault.md diff --git a/dwh-modeling/DataVault.md b/dwh-modeling/DataVault.md new file mode 100644 index 0000000..aafee6d --- /dev/null +++ b/dwh-modeling/DataVault.md @@ -0,0 +1,552 @@ +# **Data Vault 2.0: как собрать хранилище как конструктор** + +*Для тех, кто уже слышал про STG/ODS/DDS/DM, факты/измерения и SCD, но хочет разобраться, что такое Data Vault и зачем он вообще нужен.* + +--- + +## 1. Зачем вообще нужен Data Vault? + +Большинство знакомятся с хранилищами через две модели: + +* **3NF** (Инмон) — нормализованное ядро: много таблиц, строгие связи, минимум дублирования. +* **Звезда (Star Schema)** (Кимбалл) — витрины под отчёты: факт + несколько «плоских» измерений. + +Этого хватает для: + +* 2–5 источников, +* относительно стабильных схем, +* задач типа «сделать отчёт для маркетинга/финансов». + +Проблемы начинаются, когда: + +* источников **становится десяток и больше** (CRM, биллинг, ERP, сайт, мобильное приложение, партнёры, скоринги…); +* схемы **постоянно меняются**: добавляются поля, сущности, новые связи; +* появляются жёсткие требования по **аудиту и трассировке**: «покажите, откуда взялся вот этот показатель, по шагам». + +Вот тут обычная 3NF/Звезда начинает скрипеть: + +* любое изменение источника → больно по ядру и витринам; +* история размазана по разным местам (где-то SCD, где-то лог-таблицы, где-то вообще нет истории); +* добавление нового источника превращается в мини-проект на месяц. + +**Data Vault 2.0** отвечает именно на эту боль: + +> Как сделать так, чтобы **новый источник** → это не «ремонт всего дома», а просто «докрутить ещё один модуль»? + +--- + +## 2. Интуиция: DV как конструктор Lego + +Классическая метафора DV — это **конструктор из трёх типов деталей**: + +* 🔴 **Hub (Хаб)** — *«кто/что это»* + Сущности: клиент, заказ, договор, счёт. + Внутри: бизнес-ключ (`customer_id`, `contract_number`) + техполя. + +* ⚪ **Link (Линк)** — *«как они связаны»* + «Клиент сделал заказ», «договор относится к счёту». + Внутри: ссылки на хабы + техполя. + +* 🟡 **Satellite (Сателлит)** — *«какие у них свойства и как они менялись»* + Атрибуты сущности (имя, email, статус, тариф) + история изменений. + +Главная идея: + +> **Идентичность, связи и атрибуты живут отдельно.** +> Тогда изменения в одном не ломают другое. + +На картинке это выглядит примерно так: + +```mermaid +erDiagram + HUB_CUSTOMER ||--o{ SAT_CUSTOMER_INFO : "имеет атрибуты" + HUB_CUSTOMER ||--o{ LINK_ORDER_CUSTOMER : "участвует в" + HUB_ORDER ||--o{ LINK_ORDER_CUSTOMER : "создан" + LINK_ORDER_CUSTOMER ||--o{ SAT_ORDER_STATUS : "имеет историю статусов" + + HUB_CUSTOMER { + varchar customer_bk PK "Бизнес-ключ: '101'" + varchar record_source + timestamp load_dttm + } + + HUB_ORDER { + varchar order_id PK "Бизнес-ключ: '5001'" + varchar record_source + timestamp load_dttm + } + + LINK_ORDER_CUSTOMER { + varchar link_key PK "Хэш (order_id + customer_bk)" + varchar order_id FK + varchar customer_bk FK + varchar record_source + timestamp load_dttm + } + + SAT_CUSTOMER_INFO { + varchar customer_bk FK + varchar hashdiff + timestamp valid_from + timestamp valid_to + varchar name + varchar email + varchar city + varchar record_source + timestamp load_dttm + } + + SAT_ORDER_STATUS { + varchar link_key FK + varchar hashdiff + timestamp valid_from + timestamp valid_to + varchar status + varchar record_source + timestamp load_dttm + } +``` + +Как это читать: + +* 🔴 **HUB_CUSTOMER / HUB_ORDER** — «этот клиент существует», «этот заказ существует»; +* ⚪ **LINK_ORDER_CUSTOMER** — «именно этот заказ сделал именно этот клиент»; +* 🟡 **SAT_…** — как менялись атрибуты (email, статус и т.п.) во времени. + +--- + +## 3. Три типа таблиц в Data Vault 2.0 + +Чуть менее «сказочно», чуть более технично. + +### 3.1. Hub — сущность и её бизнес-ключ + +**Hub** содержит: + +* бизнес-ключ (`customer_bk`, `order_id`, `contract_number`); +* техническую информацию: + + * `record_source` — из какой системы пришла первая запись; + * `load_dttm` — когда попала в DV; + * иногда — хэш бизнес-ключа (`hk_customer`). + +Главные правила: + +* **один бизнес-ключ — один хаб** (одна строка на сущность, без истории); +* хаб не знает про атрибуты (имя, email) — только идентичность. + +Простейший DDL-скелет: + +```sql +CREATE TABLE hub_customer ( + hk_customer BYTEA PRIMARY KEY, -- хэш от BK + customer_bk VARCHAR(50) NOT NULL, -- business key + record_source VARCHAR(50) NOT NULL, + load_dttm TIMESTAMP NOT NULL +); +``` + +### 3.2. Link — связи между сущностями + +**Link** описывает факт связи, например: + +* заказ ↔ клиент, +* договор ↔ счёт, +* карта ↔ клиент. + +Примеры бизнес-смыслов: + +* `link_order_customer` — «этот заказ принадлежит этому клиенту»; +* `link_contract_account` — «этот договор привязан к этому счёту». + +DDL-приблизительно: + +```sql +CREATE TABLE link_order_customer ( + hk_order_customer BYTEA PRIMARY KEY, + hk_order BYTEA NOT NULL, + hk_customer BYTEA NOT NULL, + record_source VARCHAR(50) NOT NULL, + load_dttm TIMESTAMP NOT NULL +); +``` + +### 3.3. Satellite — атрибуты и история + +**Satellite** хранит: + +* атрибуты хаба или линка; +* историю изменений этих атрибутов. + +Примеры: + +* `sat_customer_info` — имя, email, город клиента; +* `sat_customer_segment` — сегмент, категория, риск-профиль; +* `sat_order_status` — статус заказа. + +Типичные поля: + +* ссылка на HUB/LINK (`hk_customer`, `hk_order_customer`); +* атрибуты (email, city, status…); +* `valid_from` / `valid_to` — период действия версии; +* `hashdiff` — хэш от всех атрибутов (чтобы понимать, что строка действительно изменилась, а не повторилась); +* `record_source`, `load_dttm`. + +```sql +CREATE TABLE sat_customer_info ( + hk_customer BYTEA NOT NULL, + hashdiff BYTEA NOT NULL, + valid_from TIMESTAMP NOT NULL, + valid_to TIMESTAMP NOT NULL, + name VARCHAR(100), + email VARCHAR(100), + city VARCHAR(50), + record_source VARCHAR(50) NOT NULL, + load_dttm TIMESTAMP NOT NULL +); +``` + +--- + +## 4. Типы сателлитов в DV 2.0 + +В DV 2.0 появилось разделение по «ролям» сателлитов. Главное, что стоит знать: + +* **Descriptive Satellites** — обычные атрибуты (имя, адрес, тариф) с историей. +* **Effectivity Satellites** — фокус на периодах действия (`valid_from/valid_to`), очень похоже на SCD2. +* **Multi-Active Satellites** — когда у сущности **несколько одновременных** значений (например, три активных телефона клиента). +* **Transactional Satellites** — события, привязанные к одному хабу/линку (например, журнал изменений статуса). + +На практике это разные DDL-«шаблоны» поверх одной и той же идеи: +**атрибуты + время → отдельная табличка.** + +--- + +## 5. Raw Vault vs Business Vault: «склад деталей» и «сборочный цех» + +Обычно под «Data Vault» люди смешивают два слоя: + +```mermaid +flowchart LR + SRC[Источники] --> STG[STG / ODS] + STG --> RAW[Raw Vault
Hubs, Links, Sats] + RAW --> BV[Business Vault
PIT, Bridge, Derived] + BV --> DM[Data Marts
Star Schema] + DM --> BI[BI / reports / ML] +```` + +* **Raw Vault** — это *про приём и хранение* данных «как есть», но в форме Hub/Link/Sat. +* **Business Vault** — это *про приведение их в «деловой» вид*: с бизнес-правилами, PIT/Bridge и подготовленными представлениями. + +### 5.1. Raw Vault — «всё прилетевшее, аккуратно разложенное по ящичкам» + +**Raw DV** — первый слой поверх STG/ODS: + +* выравниваем ключи; +* разбираем сущности по Hub/Link/Sat; +* сохраняем *всю* историю изменений, не решая ещё «что такое активный клиент» или «успешный заказ». + +Характерные черты Raw Vault: + +* минимум бизнес-логики: + никаких «клиент активен, если было ≥1 покупки за 90 дней»; +* все источники показываются «как есть», только приведены к общим ключам; +* структура стабильна: добавился новый источник → появился новый Sat к тому же Hub. + +Это слой **для инженеров**. Писать по нему отчёты — больно: + +* чтобы узнать «какой у клиента email на дату заказа», нужно JOIN’ить Hub + Sat + Link, плюс фильтровать по `valid_from/valid_to` или `load_dttm`; +* чтобы собрать «портрет клиента» — нужно руками клеить несколько сателлитов. + +Поэтому **Raw DV почти никогда не является точкой входа для аналитиков**. + +--- + +### 5.2. Business Vault — «там, где из Lego собирают модули» + +**Business Vault (BV)** — это слой поверх Raw DV, где: + +* появляются **бизнес-правила**, +* строятся удобные для чтения **«сборные» объекты** (таблицы и представления), +* упрощается доступ к истории. + +Кто потребитель BV: + +* разработчики витрин (DM / Star Schema); +* часть сложных отчётов (особенно с тяжёлой историей); +* иногда — data scientists, если им нужен богатый, но ещё не «сплющенный» слой. + +В BV живут несколько типичных конструкций. + +#### 5.2.1. PIT-таблицы (Point-in-Time) + +**PIT (Point-in-Time)** — таблицы, которые отвечают на вопрос: + +> «Как выглядел объект *на дату X*?» + +Вместо того чтобы каждый раз писать сложный запрос с диапазонами (`valid_from/valid_to`), мы заранее готовим таблицу: + +```sql +CREATE TABLE pit_customer_daily ( + hk_customer BYTEA, + as_of_date DATE, + hk_sat_info BYTEA, -- ссылка на нужную версию sat_customer_info + hk_sat_segment BYTEA, -- ссылка на нужную версию sat_customer_segment + ... + load_dttm TIMESTAMP +); +``` + +Теперь, чтобы собрать витрину продаж: + +* JOIN `fact_orders` к `pit_customer_daily` по `order_date = as_of_date`; +* а уже потом — к сателлитам по их ключам. + +Выигрыш: + +* сложная логика выбора «правильной версии на дату» живёт в одном месте (ETL PIT); +* BI и витрины видят «почти плоский» слой. + +#### 5.2.2. Bridge-таблицы + +**Bridge** — это «готовые маршруты» по графу Hub/Link. + +Например: + +* в DV клиент ↔ договор ↔ счёт ↔ продукт живут в разных хабах/линках; +* чтобы каждый раз не JOIN’ить весь путь, мы строим `bridge_customer_account`: + +```sql +CREATE TABLE bridge_customer_account AS +SELECT DISTINCT + hk_customer, + hk_account, + first_seen_dttm, + last_seen_dttm +FROM ... +``` + +Bridge: + +* упрощают запросы для витрин; +* фиксируют сложные правила связи (например, что считать «текущим» счётом клиента). + +#### 5.2.3. Business-правила и derived-таблицы + +В BV логично размещать бизнес-логику, которая: + +* **повторяется** во многих отчётах; +* **стабильна** относительно конкретной витрины. + +Примеры: + +* «Активный клиент» — флаг, который вычисляется на основе Raw DV (истории покупок, логинов и т.п.); +* «Основной тариф» — выбран по набору правил из нескольких источников; +* «Чистый статус заказа» — свёрнут из цепочки статусов (created → paid → shipped → delivered / cancelled). + +Это могут быть как отдельные Sats/Links, так и «логические» таблицы BV: + +```sql +CREATE TABLE bv_customer_flags AS +SELECT + hk_customer, + is_active_30d, + is_active_90d, + is_vip, + load_dttm +FROM ... +``` + +Потом витрины берут уже готовые флаги, не дублируя правила. + +--- + +### 5.3. Граница между Business Vault и витринами (DM) + +Важно не скатиться в две крайности: + +* всё тащить в Raw DV → BV пустой, витрины перегружены логикой; +* всё тащить в BV → витрины превращаются в тонкий слой SELECT’ов, но BV — новый «монолит». + +Полезное правило: + +> **В Business Vault живёт то, что относится к бизнес-сущностям и повторяется. +> В витринах живёт то, что уникально для конкретного отчёта/дашборда.** + +Например: + +* «клиент активен, если ≥1 покупки за 90 дней» — это логика уровня клиента → место ей в BV (`bv_customer_flags`); +* «клиент попал в эту конкретную маркетинговую воронку» — это логика конкретного отчёта → можно оставить в DM. + +--- + +### 5.4. Как выглядит связка Raw DV → BV → DM на примере + +Возьмём пример интернет-магазина: + +1. **Raw Vault**: + + * `hub_customer`, `hub_order`; + * `link_order_customer`; + * `sat_customer_info`, `sat_order_status`, `sat_customer_segment`. + +2. **Business Vault**: + + * `pit_customer_daily` — «срез клиента по дням»; + * `bv_customer_flags` — активность, VIP-статусы, сегменты; + * `bridge_customer_order` — связи клиент ↔ заказ с удобными ключами. + +3. **DM / Star Schema**: + + * `dm.fact_sales` — факт продаж; + * `dm.dim_customer` — уже «плоское» измерение с нужными полями (`email_current`, `segment`, `is_active_90d`…); + * `dm.dim_date`, `dm.dim_product` и т.п. + +В результате: + +* **Raw DV** — технически правильный, историчный и некрасивый; +* **BV** — «рабочий слой для инженеров и продвинутых аналитиков»; +* **DM** — привычная Звезда для всех остальных. + +--- + +Кратко: + +* Raw DV без BV — это как держать только структуры `hub_*/link_*/sat_*` и заставлять всех писать поверх них запросы. Это больно. +* **Business Vault — обязательный промежуточный слой**, если вы действительно живёте в Data Vault, а не просто «сложили историю по паттерну Hub/Link/Sat». + +--- + +## 6. Пример: клиент и заказы в DV-стиле + +Возьмём мини-пример (тот же интернет-магазин): + +* клиент с `customer_id = 101` и меняющимся email; +* заказы `order_id = 5001, 5002`; +* статусы заказов. + +В Data Vault: + +* `hub_customer` — одна строка на BK `101`; +* `sat_customer_info` — несколько строк по мере смены email/города; +* `hub_order` — по строке на каждый заказ; +* `link_order_customer` — связь заказ ↔ клиент; +* `sat_order_status` — история статусов заказа. + +Дальше: + +* из Raw DV мы строим PIT: + + * «каким был клиент на дату заказа?»; +* и уже из PIT + ссылок собираем витрину `mart_sales` в формате Звезды. + +Тут важно, что **Raw DV почти не трогается** при изменении бизнес-логики — правим BV и витрины. + +--- + +## 7. Как это живёт в пайплайне загрузки + +Типичный ETL/ELT с DV: + +1. **STG/ODS**: вытащили данные из источников, почистили типы, привели формат. +2. **Raw Vault**: + + * по BK вычислили хэш-ключи для Hubs; + * создали/обновили Hubs; + * создали/обновили Links; + * сравнили `hashdiff` в Sats → добавили новые версии атрибутов. +3. **Business Vault**: + + * собрали PIT-таблицы (одна строка на объект на дату X); + * добавили бизнес-флаги и derived-поля. +4. **DM (Star)**: + + * построили факт + измерения, которые уже подходят BI/аналитикам. + +За счёт **хэш-ключей и hashdiff** DV хорошо масштабируется и параллелится: +разные сущности и домены могут грузиться независимыми пайплайнами. + +--- + +## 8. Плюсы и минусы Data Vault — без романтики + +### 8.1. Плюсы + +* 📜 **История «из коробки»** + Каждое изменение — отдельная запись в сателлите. Ничего не перезатирается. + +* 🧩 **Лёгкое добавление источников** + Новый источник с теми же сущностями = новые сателлиты к тем же хабам. + +* 🔎 **Трассировка и аудит** + Видно, из какого источника, когда и с какими атрибутами прилетела каждая строка. + +* 👥 **Параллельная работа команд** + Разные домены в DV практически не блокируют друг друга. + +### 8.2. Минусы + +* 🧠 **Высокий порог входа** + Нужно понимать SCD, хэш-ключи, нагрузку на JOIN, паттерны загрузки. + +* 📈 **Больше таблиц и JOIN’ов** + Даже простой запрос превращается в «HUB + LINK + 2–3 SAT + PIT». + +* 🛠 **Нужна дисциплина** + Забыли заполнить `record_source`/`load_dttm` — потеряли часть аудита. + +* ⏳ **Плохо подходит для MVP** + Для 2–3 источников DV обычно дороже, чем классическая 3NF/Звезда. + +--- + +## 9. Когда DV стоит использовать, а когда — нет + +### Подходит, если: + +* у вас **зоопарк источников** (5+ систем, которые ещё и меняются); +* важна **полная история и аудит** (финтех, гос, телеком, крупный банк); +* команда нацелена на долгую жизнь DWH, а не одноразовый отчёт; +* есть люди, готовые жить в этой модели (архитектор, data engineer’ы). + +### Лучше не начинать с DV, если: + +* это **первый DWH в компании**; +* 1–3 источника и нет жёстких требований по аудиту; +* команда малая (1–2 инженера + аналитик) и сроки жмут; +* задача звучит как «дайте отчёты к кварталу», а не «построим платформу на 5 лет». + +В таких случаях честнее (и дешевле) начать с: + +> `stg → ods → dds (3NF/простая Звезда с SCD2) → dm (Звезда)` + +А DV оставить как следующий шаг, когда появятся реальные боли, которые он решает. + +--- + +## 10. Как учить Data Vault дальше + +Если после этой статьи хочется «копнуть глубже», можно идти по такой траектории: + +1. **Повторить базу**: + + * слои STG/ODS/DDS/DM; + * факты/измерения; + * SCD Type 2. + +2. **Почитать/посмотреть про DV 2.0**: + + * книги/доки Линстедта (Dan Linstedt, *Data Vault 2.0*), + * практические доклады (особенно от телекомов и банков — там DV живой). + +3. **Сделать игрушечный DV-проект**: + + * взять тот же интернет-магазин, + * смоделировать `hub_customer`, `hub_order`, `link_order_customer`, 2–3 сателлита, + * написать пару запросов: «какой был клиент на дату заказа?», «как менялся статус заказа?». + +4. **Посмотреть на гибриды**: + + * DV в ядре (Raw+Business Vault), + * Звезда на витринах. +