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 загрузка 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 key | Business key | Hash 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 не критичны.
Не путайте две разные вещи. Криптографическая «сломанность» MD5 — это про намеренное построение коллизии злоумышленником. В Data Vault входные данные — это бизнес-ключи из ваших источников, никто их специально не подбирает. Поэтому реальный риск MD5 здесь — не атака, а статистика на очень больших объёмах. И всё же при свободном выборе берите SHA-256: причин предпочесть MD5, кроме экономии 16 байт, практически нет.
Попробуй сам
- Возьмите три business key своей предметной области (например,
CUST-001,ORD-5501,SKU-ABC). Для каждого мысленно пройдите шаги вычисления hash key: нормализация (UPPER,TRIM), затем хеш. - Для Link «заказ-товар» составьте строку-вход для хеша из двух business keys с разделителем. Объясните себе, почему без разделителя пара (
ORD-55,01SKU) и пара (ORD-5501,SKU) дали бы один хеш. - Прикиньте: у вас 100 млн строк в крупнейшем Link. Сколько байт «весит» столбец hash key при SHA-256 (32 байта на строку)? А при sequence-ключе (8 байт)? Это та самая разница в стоимости хранения, о которой спорят практики.
- Сформулируйте в двух предложениях, в каком проекте вы бы выбрали hash key, а в каком задумались бы о sequence или business key.
В следующем уроке разберём hashdiff — отдельный хеш, который Data Vault использует не для ключей, а для обнаружения изменений в Satellite.