Learning Platform
Глоссарий Troubleshooting
Урок 17.03 · 18 мин
Начальный
data-vaulthash-keyssurrogate-keys

Hash keys, sequence keys и business keys

Во всех таблицах из прошлого урока — hub_customer, link_order_customer, sat_customer_details — первичным ключом был не привычный целочисленный счётчик, а столбец с суффиксом _hk типа BYTEA: hash key. Это сознательное решение Data Vault 2.0, и за ним стоит конкретный компромисс. В этом уроке разберём три варианта ключа для Data Vault — hash, sequence, business — поймём, почему DV2.0 сделал hash базовым паттерном, и честно посмотрим, где этот выбор спорный.

Напомним проблему из урока про ключи в начале курса. Surrogate key — искусственный идентификатор без бизнес-смысла. Способов его сгенерировать несколько, и для Data Vault выбор способа критичен, потому что от него зависит, можно ли грузить hubs, links и satellites параллельно.


Три кандидата на роль ключа

Sequence key — монотонно растущий целочисленный счётчик. Так работает классический IDENTITY или SEQUENCE в реляционной БД: 1, 2, 3, … Компактно (4-8 байт), быстрый JOIN. Но есть фундаментальная проблема для Data Vault: счётчик централизован. Чтобы выдать следующее число, нужен один источник истины — генератор последовательности. Два параллельных воркера не могут независимо присвоить ключи: им придётся координироваться через этот генератор.

Business key — использовать сам бизнес-ключ как первичный (номер клиента CUST-001 напрямую как PK хаба). Никакой генерации не нужно, ключ осмысленный. Но business key бывает длинным, составным, строковым — а это широкий ключ: больше байт в каждом индексе, в каждом Link, в каждом Satellite. И если бизнес-ключ составной (например, source_system + customer_number), JOIN идёт по нескольким столбцам.

Hash key — взять бизнес-ключ и пропустить через хеш-функцию (в Data Vault 2.0 это MD5 или SHA-256). Результат — значение фиксированной ширины: 16 байт для MD5, 32 для SHA-256. Это и есть базовый паттерн Data Vault 2.0.

-- Hash key хаба = хеш от нормализованного business key
SELECT
    SHA256(UPPER(TRIM('CUST-001'))) AS customer_hk,
    'CUST-001'                       AS customer_bk;

-- customer_hk: 0x9c1f2a... (32 байта, всегда одинаковой длины)
-- customer_bk: CUST-001

-- Hash key линка = хеш от КОМБИНАЦИИ business keys связываемых хабов
SELECT SHA256(
    UPPER(TRIM('ORD-5501')) || '||' || UPPER(TRIM('CUST-001'))
) AS order_customer_hk;
-- || '||' || — разделитель, чтобы 'AB'+'C' и 'A'+'BC' не дали один хеш

Два правила вычисления хеша критичны. Первое — нормализация перед хешированием: business key приводят к единому виду (UPPER, TRIM), иначе cust-001 и CUST-001 дадут разные хеши и одна сущность задвоится. Второе — разделитель между полями составного ключа: без него конкатенация 'AB' + 'C' и 'A' + 'BC' даст одну строку и один хеш — ложное совпадение.


Почему hash key — базовый паттерн DV2.0

Главное достоинство hash key — детерминированность. Хеш-функция всегда возвращает одно и то же значение для одного входа. Это значит: любой процесс, в любом порядке, на любой машине, зная только business key, вычислит ровно тот же hash key — без обращения к чему-либо ещё.

Отсюда — ключевое для Data Vault свойство: независимая параллельная загрузка.

Sequence key против hash key при параллельной загрузке
Воркер A: hubГрузит хаб. С sequence key обязан спросить счётчик
ждёт счётчик
Генератор sequenceЦентрализованный источник номеров — узкое место и точка координации
ждёт счётчик
Воркер B: linkГрузит линк. Тоже обязан спросить счётчик
с hash key координатор не нужен
Воркер A: hubСчитает hash сам из business key, никого не спрашивает
Воркер B: linkСчитает тот же hash из тех же business keys независимо
Воркер C: satelliteСчитает hash родителя сам, грузит параллельно

С sequence key загрузка Link требует знать sequence-ключи хабов — значит, хабы должны быть загружены раньше, и нужен общий генератор. С hash key воркер, грузящий Link, сам вычисляет hash keys хабов из business keys прямо в строке источника. Хабы, линки и сателлиты грузятся одновременно, независимо, без координации и без сети между воркерами. Это и есть причина, по которой DV2.0 сделал hash базовым: при десятках источников и большом объёме параллельная загрузка — не роскошь, а необходимость.

Второе достоинство — фиксированная ширина. Hash key всегда 16 или 32 байта, независимо от того, насколько длинный и составной исходный business key. JOIN идёт по одному столбцу фиксированного размера — предсказуемо и компактно в индексах.

Третье — воспроизводимость между средами. Один business key даёт один hash и в dev, и в prod. Можно пересобрать Vault или сверить среды, не таская таблицы соответствия ключей.


Где выбор спорный: hash — это компромисс, а не победа

Важно не подавать hash key как «hash заменил sequence, вопрос закрыт». Это живой компромисс, и среди практиков идёт реальная дискуссия — особенно с приходом облачных warehouse вроде Snowflake.

У hash key есть цена:

  • Вычисление хеша стоит CPU. На каждой строке каждого источника считается MD5/SHA-256. На больших объёмах это заметная нагрузка.
  • Хранение шире, чем у sequence. 16-32 байта против 4-8 у целого числа. На warehouse, где платят за хранение и за сканирование, лишние байты в каждом Link и Satellite складываются в реальные деньги.
  • Хуже локальность, чем у монотонного ключа. Хеш по своей природе равномерно «разбросан», тогда как sequence монотонно растёт. Для индексов и кластеризации монотонный ключ иногда выгоднее.

Поэтому на современных колоночных warehouse идёт предметный спор «to hash or not to hash»: часть команд на Snowflake/BigQuery возвращается к sequence-подобным или просто к business keys, потому что эти движки и так хорошо сжимают и соединяют, а компьют на хеширование экономится. Hash остаётся рекомендованным паттерном Data Vault 2.0 и базовым выбором по умолчанию — но называть его однозначным победителем над sequence некорректно.

КритерийSequence keyBusiness keyHash key
Параллельная загрузкаНет (нужен координатор)ДаДа
Ширина ключаУзкая (4-8 байт)Переменная, бывает большойФиксированная (16-32 байта)
Стоимость генерацииНизкаяНулеваяCPU на хеширование
Воспроизводимость между средамиНетДаДа
Локальность для индексовВысокая (монотонный)ЗависитНиже (разбросан)

Коллизии: теория и практика

Раз hash key — это хеш, неизбежен вопрос: что если два разных business key дадут один хеш? Это коллизия, и обращаться с этим страхом нужно трезво.

Коллизии существуют в теории — это следствие так называемого «парадокса дней рождения»: вероятность совпадения растёт быстрее, чем кажется интуиции, потому что считается не «новый ключ против одного старого», а против всех уже существующих. Но «существует в теории» и «случится у вас» — разные вещи.

Для SHA-256 пространство значений — 2 в степени 256. Это число настолько превосходит количество строк в любом мыслимом хранилище, что вероятность коллизии остаётся пренебрежимо малой даже при триллионах ключей. На практике коллизия SHA-256 на бизнес-данных — не проблема: с ней не сталкиваются.

MD5 — более слабый выбор. Его пространство — 2 в степени 128, и сама функция давно скомпрометирована криптографически (известны способы намеренно построить коллизию). Для случайных бизнес-ключей MD5 на типичных объёмах тоже обычно не даёт коллизий, и исторически Data Vault 2.0 его допускал ради компактности (16 байт против 32). Но если выбираете сегодня — SHA-256 предпочтительнее: запас прочности несопоставимо больше, а 32 байта на современном warehouse не критичны.

WARNING

Не путайте две разные вещи. Криптографическая «сломанность» MD5 — это про намеренное построение коллизии злоумышленником. В Data Vault входные данные — это бизнес-ключи из ваших источников, никто их специально не подбирает. Поэтому реальный риск MD5 здесь — не атака, а статистика на очень больших объёмах. И всё же при свободном выборе берите SHA-256: причин предпочесть MD5, кроме экономии 16 байт, практически нет.

Surrogate key в dbt — как dbt_utils.generate_surrogate_key реализует логику hash key

Попробуй сам

  1. Возьмите три business key своей предметной области (например, CUST-001, ORD-5501, SKU-ABC). Для каждого мысленно пройдите шаги вычисления hash key: нормализация (UPPER, TRIM), затем хеш.
  2. Для Link «заказ-товар» составьте строку-вход для хеша из двух business keys с разделителем. Объясните себе, почему без разделителя пара (ORD-55, 01SKU) и пара (ORD-5501, SKU) дали бы один хеш.
  3. Прикиньте: у вас 100 млн строк в крупнейшем Link. Сколько байт «весит» столбец hash key при SHA-256 (32 байта на строку)? А при sequence-ключе (8 байт)? Это та самая разница в стоимости хранения, о которой спорят практики.
  4. Сформулируйте в двух предложениях, в каком проекте вы бы выбрали hash key, а в каком задумались бы о sequence или business key.

В следующем уроке разберём hashdiff — отдельный хеш, который Data Vault использует не для ключей, а для обнаружения изменений в Satellite.


Проверка знанийKnowledge check
Почему hash key позволяет грузить hubs, links и satellites параллельно и независимо, а sequence key — нет?
ОтветAnswer
Hash key детерминирован: хеш-функция всегда возвращает одно и то же значение для одного входного business key. Поэтому любой процесс, на любой машине, в любом порядке может сам вычислить нужный hash key, зная только business key из строки источника — ему не нужно ни к кому обращаться. Воркер, грузящий Link, вычисляет hash keys связываемых хабов прямо из business keys, не дожидаясь, пока хабы будут загружены. В результате hubs, links и satellites грузятся одновременно, без координации и без сети между воркерами. Sequence key, напротив, выдаётся централизованным генератором последовательности — единственным источником монотонных номеров. Чтобы Link сослался на хаб по sequence-ключу, хаб должен быть загружен раньше (чтобы ключ существовал), а сам генератор становится точкой координации и узким местом. Параллельная независимая загрузка с sequence key поэтому невозможна — и именно из-за этого Data Vault 2.0 сделал hash key базовым паттерном.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Главное свойство hash key, ради которого Data Vault 2.0 сделал его базовым паттерном, — это:

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5