Hubs, Links и Satellites
В прошлом уроке мы выяснили, зачем нужен Data Vault: auditability и устойчивость к изменениям. Эти свойства не берутся из воздуха — они прямое следствие того, как Data Vault раскладывает данные по таблицам. Вся модель состоит ровно из трёх типов структур: Hub, Link и Satellite. Любая, даже огромная Data Vault-схема — это комбинация этих трёх кирпичей. Разберитесь с ними один раз, и читать Vault-схемы станет легко.
Главная идея разделения: каждый тип структуры отвечает за один аспект данных. Hub — за идентичность сущности. Link — за связи между сущностями. Satellite — за описательные атрибуты и их изменение во времени. То, что в нормализованной таблице слито в одну строку (ключ + связи + атрибуты), Data Vault сознательно разносит. Именно это разнесение даёт независимую загрузку и устойчивость к изменениям.
Hub: идентичность бизнес-сущности
Hub хранит уникальный список business keys одной сущности — и больше ничего описательного. Business key — это идентификатор сущности в терминах бизнеса: номер клиента, артикул товара, номер заказа. Не surrogate key из source-системы, а именно то, чем сущность опознаётся в реальном мире.
Hub содержит ровно четыре вида столбцов:
CREATE TABLE hub_customer (
customer_hk BYTEA NOT NULL, -- hash key: PK хаба
customer_bk VARCHAR(50) NOT NULL, -- business key: номер клиента
load_date TIMESTAMP NOT NULL, -- когда ключ впервые увиден
record_source VARCHAR(50) NOT NULL, -- источник первого появления
PRIMARY KEY (customer_hk)
);
-- Пример содержимого:
-- customer_hk | customer_bk | load_date | record_source
-- 0x8f3a... | CUST-001 | 2026-01-10 02:00:00 | crm.customers
-- 0x1c7e... | CUST-002 | 2026-01-10 02:00:00 | crm.customers
-- 0x4b90... | CUST-003 | 2026-01-12 02:00:00 | shop.users
Обратите внимание: в Hub нет имени, email, города — никаких атрибутов. Только business key, его hash key (об этом — следующий урок) и метаданные. Hub отвечает на единственный вопрос: «существует ли такая сущность и когда мы впервые её увидели». Один Hub на одну бизнес-сущность: hub_customer, hub_product, hub_order. Если business key одного клиента приходит из CRM и из интернет-магазина — в Hub всё равно одна строка, потому что сущность одна. Это делает Hub точкой интеграции источников.
Link: связи между сущностями
Link хранит связь — отношение между двумя или более business keys. Заказ принадлежит клиенту: это связь между hub_order и hub_customer. Заказ содержит товары: связь между hub_order и hub_product.
Важнейшее свойство: в Data Vault все Link-таблицы по структуре many-to-many. Link не накладывает ограничений на кардинальность. Если сегодня связь «заказ-клиент» — это 1:N, а завтра бизнес решит, что заказ может принадлежать двум клиентам — модель менять не нужно, Link это уже допускает. Это часть устойчивости Data Vault к изменениям.
CREATE TABLE link_order_customer (
order_customer_hk BYTEA NOT NULL, -- собственный hash key связи
order_hk BYTEA NOT NULL, -- ссылка на hub_order
customer_hk BYTEA NOT NULL, -- ссылка на hub_customer
load_date TIMESTAMP NOT NULL,
record_source VARCHAR(50) NOT NULL,
PRIMARY KEY (order_customer_hk)
);
-- Пример содержимого:
-- order_customer_hk | order_hk | customer_hk | load_date | record_source
-- 0xaa12... | 0xord1... | 0x8f3a... | 2026-01-15 02:00:00 | shop.orders
-- 0xbb34... | 0xord2... | 0x1c7e... | 2026-01-15 02:00:00 | shop.orders
Как и Hub, Link не содержит описательных атрибутов. У него есть собственный hash key, hash keys связываемых хабов и метаданные. Link отвечает на вопрос «какие сущности связаны». А вот что это была за связь по сути — её атрибуты, дата, сумма — хранится отдельно, в Satellite на этом Link.
Hubs и Links вместе образуют каркас модели — скелет из ключей и связей, без единого описательного атрибута. На этот каркас навешиваются Satellites.
Satellite: атрибуты и история
Satellite хранит описательные атрибуты — и их историю во времени. Имя клиента, его email, город, статус заказа, сумма — всё это живёт в Satellites. Satellite всегда привязан к одному родителю: либо к Hub, либо к Link.
Вот где проявляется insert-only из прошлого урока. Когда атрибут меняется, Satellite не обновляет строку — он добавляет новую с новым load_date. Старая строка остаётся. Так Satellite накапливает полную историю изменений.
CREATE TABLE sat_customer_details (
customer_hk BYTEA NOT NULL, -- hash key хаба-родителя
load_date TIMESTAMP NOT NULL, -- начало действия версии
load_end_date TIMESTAMP, -- конец действия (NULL = текущая)
record_source VARCHAR(50) NOT NULL,
hashdiff BYTEA NOT NULL, -- хеш атрибутов (урок 4)
full_name VARCHAR(200),
email VARCHAR(200),
city VARCHAR(100),
PRIMARY KEY (customer_hk, load_date)
);
-- Клиент CUST-001 переехал из Москвы в Казань:
-- customer_hk | load_date | load_end_date | full_name | city
-- 0x8f3a... | 2026-01-10 | 2026-03-20 | Анна Петрова| Москва
-- 0x8f3a... | 2026-03-20 | NULL | Анна Петрова| Казань
Первичный ключ Satellite — это (hash key родителя, load_date). Пара «кто» + «когда» уникальна: на один момент времени у сущности ровно одна версия атрибутов. У текущей версии load_end_date равен NULL (или дате-заглушке 9999-12-31).
К одному Hub можно подключить несколько Satellites — и это нормальная практика. Атрибуты часто разделяют по двум критериям: по источнику и по скорости изменения. Если клиент описан и в CRM, и в биллинге — делают sat_customer_crm и sat_customer_billing: каждый источник грузит свой Satellite независимо. Если часть атрибутов меняется редко (имя, дата рождения), а часть часто (статус, баланс лояльности) — их тоже разносят: так редкие изменения не раздувают строки вместе с частыми.
Простое правило различения: если структура хранит business key — это Hub. Если хранит связь между ключами — это Link. Если хранит описательные атрибуты — это Satellite. Hub и Link описательных атрибутов не содержат никогда. Это и есть весь словарь Data Vault.
Как три блока работают вместе
Соберём картину целиком на примере «клиент сделал заказ».
- hub_customer и hub_order держат идентичность двух сущностей.
- link_order_customer фиксирует, что конкретный заказ связан с конкретным клиентом.
- sat_customer_details хранит, как менялись данные клиента; sat_order_details — как менялся статус заказа (created -> paid -> shipped).
Чтобы собрать «полную картину заказа» для витрины, нужно соединить Hub + Link + нужные Satellites. Запросы к Data Vault многословны — это плата за гибкость. Поэтому Data Vault и не показывают аналитику напрямую: поверх него строят витрины, где всё уже соединено.
Сравним подходы напрямую:
| Аспект | Нормализованная таблица | Data Vault |
|---|---|---|
| Ключ, связи, атрибуты | В одной таблице | Hub / Link / Satellite раздельно |
| Изменение атрибута | UPDATE строки | INSERT строки в Satellite |
| Новый источник атрибутов | Менять существующую таблицу | Добавить новый Satellite |
| История изменений | Нет (если не SCD) | Есть всегда |
| Запросы | Простые | Многословные (много JOIN) |
Заметьте симметрию с тем, что вы уже знаете: Hub похож на dimension без атрибутов, Satellite — на хранилище версий в духе SCD2, Link — на junction table из урока про кардинальность. Data Vault не изобретает новые понятия — он по-новому их комбинирует ради auditability и параллельной загрузки.
Попробуй сам
Возьмите предметную область из прошлого урока (интернет-магазин) и спроектируйте фрагмент Data Vault на бумаге:
- Выпишите бизнес-сущности и для каждой создайте Hub:
hub_customer,hub_product,hub_order. Укажите business key каждой. - Найдите связи между сущностями и создайте Links. Минимум два:
link_order_customer(заказ-клиент) иlink_order_product(заказ-товар, заведомо many-to-many). - Для каждого Hub и каждого Link придумайте Satellite с описательными атрибутами. У заказа атрибуты статуса вынесите в отдельный Satellite от остальных — статус меняется часто.
- Проверьте себя: убедитесь, что ни в одном Hub и ни в одном Link нет описательных атрибутов. Если есть — перенесите их в Satellite.
В следующем уроке разберём, что за hash key стоит во всех этих таблицах вместо обычного целочисленного ключа и почему.