Привет, Хабр! Меня зовут Алексей Миронов, я главный разработчик отдела разработки хранилищ данных в «Газпром ЦПС». Сегодня я расскажу о своем опыте внедрения Data Vault в компании.
Методология Data Vault 2.0 на бумаге выглядит безупречно, особенно если ваша команда живёт по Agile. Разделение данных на Хабы, Линки и Сателлиты позволяет расширять хранилище инкрементально. Появился новый источник или изменилась бизнес‑логика? Просто достраиваем новые блоки рядом, не ломая старые сущности и не переписывая половину DWH, как это часто бывает в классической архитектуре Кимбалла.
Кроме того, Data Vault даёт чёткие правила игры: стандарты генерации объектов, расчёта хэш‑ключей и версионирования истории здесь прописаны до нас. Это полностью убирает «творчество» отдельных инженеров — вся команда пишет код в едином стандарте.
Но когда дело доходит до практики, начинаются сложности. Ручное проектирование однотипных таблиц быстро превращается в ад. В этой статье я расскажу, как мы наступили на все классические грабли ручного Data Vault и как написали собственный гибкий фреймворк автоматизации.
Ожидания vs Реальность: с какими болями мы столкнулись
К проекту мы решили подойти основательно. Подготовку начали с теории: специально купили легендарную книгу Дэна Линстеда «Building a Scalable Data Warehouse with Data Vault 2.0» в оригинале. Честно прочитали (признаюсь, местами сильно по диагонали) и, вооружившись академическими знаниями, бесстрашно ринулись в бой с реальными данными.
На бумаге всё выглядело гладко, но как только книжная теория столкнулась с продакшен‑выгрузками, мы моментально упёрлись в классические проблемы роста:

Отсутствие практического опыта. Одно дело — читать теорию, и совсем другое — раскладывать грязные бизнес‑данные по Хабам и Сателлитам. У команды не было набитых шишек, поэтому правила моделирования приходилось нащупывать на ходу.
Распределение атрибутов. Мы постоянно спорили: «В какой именно сателлит положить конкретное поле? Делать под него отдельный сателлит по скорости изменения или объединить с базовым?» Ошибки в таких решениях приводили в архитектурные тупики.
Бесконечный рефакторинг опытным путём. Ошибки проектирования вскрывались только после запуска пайплайнов. Опишем модель, запустим, поймём, что логика хромает, удалим таблицы, перепишем SQL руками — и по новой.
Человеческий фактор. При ручном написании DDL инженеры регулярно забывали добавить технические поля, путали типы или пропускали индексы на исторические интервалы.
Когда цикл «написал руками SQL → ошибся → дропнул → переписал» повторился в тридцатый раз, стало очевидно: рутина убивает всю гибкость Agile. Команда тратит время на монотонный SQL‑код вместо проектирования архитектуры. Процесс нужно было срочно автоматизировать.
Почему не Automate_dv, Vaultspeed и другие готовые решения?
Мы изучили рынок, но сознательно отказались от внешних инструментов по трём причинам:
1. Безопасность и изоляция. В корпоративном контуре нельзя просто взять сторонний фреймворк — он требует аудита, проверки зависимостей и согласований с ИБ. Наш движок на чистом PL/pgSQL работает внутри СУБД, не требует внешних доступов и полностью прозрачен.
2. Комплаенс и санкционные риски. Проприетарные зарубежные решения (Vaultspeed) отпали из‑за геополитики и отсутствия поддержки. Опенсорсные плагины для dbt тоже не прошли бы внутренние бюрократические фильтры — легализация заняла бы месяцы. Свой инструмент оказался быстрее и безопаснее.
3. Стоимость владения и контроль. У нас нет бюджета на внешних консультантов и интеграторов. Любое готовое решение требует доработок и сопровождения, а разбираться в чужом коде за свои деньги — невыгодно. Собственный движок мы контролируем полностью, чиним и дорабатываем силами пары внутренних инженеров без лишних расходов.

Архитектура, управляемая метаданными (Metadata‑Driven)
Мы решили полностью отвязать проектирование бизнес‑модели от написания SQL. Нужна была система, где можно изменить структуру объекта в одном месте, запустить одну функцию — и база данных сама перестроится под новые требования.

Отказались от тяжёлых внешних фреймворков и спроектировали лаконичную реляционную схему метаданных из четырёх таблиц в схеме meta:
meta.project— верхнеуровневый контейнер для доменов данных. Хранит имя проекта и имя целевой схемы в БД, где будут создаваться физи1ческие таблицы.meta.entity— описание целевой таблицы. Для нашего движка сущность — это любой физический объект Data Vault. Допустимые типы строго ограничены:HUB,LNKилиSAT. Важнейшее поле здесь — порядковый номер (ord_no), который задаёт последовательность создания и удаления объектов.meta.entity_columns— атомарный состав физического слоя: имена колонок, их типы, длины и алиасы.meta.entity_links— таблица связей, которая через внешние ключи указывает, как объекты связаны между собой (какие Хабы объединяет Линк, или к какому объекту привязан Сателлит).
Сердце движка: генераторы DDL на PL/pgSQL
В качестве инструмента автоматизации мы выбрали нативный PL/pgSQL. Это позволило выполнять генерацию DDL “на лету” прямо внутри СУБД, без привлечения внешних скриптов.
Задача функций — не просто собрать текстовую строку, а полностью автоматизировать создание типовых индексов, ключей, констрейнтов и унифицированных наименований таблиц, чтобы исключить человеческий фактор. На каждый тип сущностей написали свою изолированную функцию.
1. Генерация Хабов
Функция вычитывает бизнес‑ключи из метаданных, склеивает их в единую строку с проверкой типов данных, автоматически добавляет системные поля Data Vault 2.0 (хэш‑ключ хаба, системное время загрузки, источник записи и идентификаторы процессов) и выполняет динамическое создание таблицы. Также автоматически вешается индекс на поле времени загрузки для будущих инкрементов.
2. Генерация Линков
Линк связывает таблицы «многие ко многим». Наш генератор обращается к метаданным связей, находит все ассоциированные Хабы, динамически формирует UUID‑колонки для их хэш‑ключей и прописывает строгие FOREIGN KEY на родительские таблицы. Система автоматически учитывает дескрипторы связей, если один и тот же хаб участвует в линке несколько раз в разных бизнес‑ролях.
3. Генерация Сателлитов
Самая перегруженная часть ручного кодинга. Функция автоматически связывает Сателлит с родителем, формирует составной первичный ключ по хэш‑ключу родителя и времени загрузки, добавляет обязательный хэш‑дифф для контроля изменений, а также интервалы версионирования для системного и бизнес‑времени. В качестве бонуса генератор сразу создаёт два составных индекса для оптимизации будущих тяжёлых исторических выборок.
Оркестрация деплоя: управляемый хаос
Объекты Data Vault нельзя создавать или удалять в случайном порядке — СУБД моментально заблокирует операцию из‑за нарушений внешних ключей. Для решения этой проблемы мы написали две функции оркестрации, завязав их на поле порядкового номера (ord_no).

Когда нужно полностью пересоздать структуру проекта (например, после изменения гипотез моделирования), мы сначала запускаем функцию очистки. Она читает метаданные и аккуратно удаляет таблицы в обратном порядке (от большего ord_no к меньшему). Сначала уничтожаются зависимые сателлиты, затем линки и только потом — независимые хабы.
Вслед за очисткой запускается функция деплоя проекта. Она идёт по прямому порядку (по возрастанию ord_no). Здесь мы применили динамический полиморфизм метапрограммирования. Вместо громоздких ветвлений имя исполняемой функции‑генератора собирается на лету из строки типа сущности в метаданных: движок сам понимает, какую именно функцию (f_create_hub, f_create_lnk или f_create_sat) вызвать для текущей сущности.
Вишенка на торте — автоматизация DML‑слоя: расчёт Hash Key и Hash Diff для массива атрибутов
В методологии Data Vault 2.0 расчёт хэш‑ключей и хэш‑диффов — это основа. Чтобы получить эталонный хэш, нужно взять набор бизнес‑ключей (или описательных колонок для сателлита), применить трансформации, склеить через специальный разделитель.

Нам требовался универсальный инструмент, который умеет делать это для любого массива атрибутов произвольного состава и длины, на лету подстраиваясь под структуру конкретной таблицы. Штатные функции СУБД вроде приведения всей строки к тексту здесь категорически не подходят: они чувствительны к физическому порядку колонок и ломаются на NULL‑значениях. Готовых аналогов мы не нашли, поэтому написали свою связку.
select md5( lower( 'Постгрессов Констрэйнтин Вакуумович' ||'^'|| 'Датаинженер' ) ) md5 | --------------------------------+ 0d6e25195d529dca6723ed5511dd84ed| -- если изменить порядок ключей, то получим другой hkey select md5( lower( 'Датаинженер'||'^'|| 'Постгрессов Констрэйнтин Вакуумович' ) ) md5 | --------------------------------+ 8344090c20bb1b420c2856cd7ba4bddb|
Мы создали кастомный составной тип данных, который хранит имя ключа, его значение и порядковый номер. А затем написали две функции, принимающие на вход массив этих атрибутов:
Сборка эталонной строки изменений. Функция разворачивает входящий массив в плоскую структуру. Внутри агрегатора происходит жёсткая сортировка элементов по порядковому номеру и имени. Это самое главное: в каком бы порядке атрибуты ни пришли на вход, строка изменений всегда соберётся в строго детерминированной последовательности. Все значения очищаются от пробелов, приводятся к нижнему регистру и защищаются от NULL‑значений, склеиваясь через разделитель
^.Генерация эталонного UUID. Использует ту же логику сортировки, но на финальном шаге оборачивает полученную эталонную строку в алгоритм хэширования, возвращая компактный и чистый UUID.
select meta.make_hash_key( array[ row(0,'position', 'Датаинженер')::hash_data, row(0,'fio', 'Постгрессов Констрэйнтин Вакуумович')::hash_data ] ) make_hash_key | ------------------------------------+ 0d6e2519-5d52-9dca-6723-ed5511dd84ed| -- теперь если меняем порядок ключей – hkey не изменяется select meta.make_hash_key( array[ row(0,'fio', 'Постгрессов Констрэйнтин Вакуумович')::hash_data, row(0,'position', 'Датаинженер')::hash_data ] ) make_hash_key | ------------------------------------+ 0d6e2519-5d52-9dca-6723-ed5511dd84ed|
No‑code для архитекторов и аналитиков: FastAPI + SQLAdmin
Когда движок генерации DDL и хэшей на PL/pgSQL был отлажен, мы столкнулись с новым вызовом. Нашим архитекторам и аналитикам нужно было как‑то наполнять эту базу метаданных.
Заставлять людей писать ручные INSERT‑ы (генерировать с помощью Excel) или связывать ключи через сырые ID таблиц — это путь к новым ошибкам. Нужен был удобный интерфейс, где можно кликами собирать спецификации будущих таблиц.
Чтобы не тратить месяцы на разработку фронтенда, мы пошли по пути быстрого прототипирования и склепали лёгковесный no‑code интерфейс. В качестве бэкенда взяли FastAPI, для визуальной части — SQLAdmin, которая автоматически генерирует админ‑панель на основе моделей SQLAlchemy.
За пару дней мы реализовали полноценный визуальный CRUD для всех таблиц метаданных. Теперь аналитик или архитектор заходит в браузер и в удобной форме:
создаёт или выбирает Проект;
добавляет Сущность, выбирая её тип (
HUB,LNKилиSAT) из выпадающего списка;накликивает Колонки и их типы данных, не боясь ошибиться в синтаксисе;
визуально настраивает Связи (Links), выбирая мышкой, какие Хабы должны объединяться.

Как только спецификация готова, архитектор нажимает кнопку «Передеплоить проект», и под капотом запускается наш оркестратор. Спецификация из админки мгновенно превращается в физические таблицы Data Vault в нужной схеме. Это окончательно стёрло барьер между проектированием модели и её воплощением в базе.
Чего мы добились в итоге
Внедрение собственного лёгковесного фреймворка полностью изменило рабочий процесс команды:
Скорость выросла кардинально. Проблема долгого рефакторинга «опытным путём» ушла. Если понимаем, что ошиблись со структурой или распределением полей, просто правим строки в no‑code интерфейсе и за пару секунд перезапускаем деплой. На выходе — идеально чистая, обновлённая структура. Сейчас наш список содержит порядка 100 сущностей, данные из 4 доменов.
Тотальная стандартизация. Полностью исключён человеческий фактор. База данных генерируется по единому стандарту — со всеми техническими полями, хэш‑диффами, констрейнтами и правильными индексами.
Фокус на важном. Мы перестали быть «машинистками», набивающими терабайты SQL‑кода. Инженеры данных наконец сосредоточились на архитектуре, качестве данных и проектировании бизнес‑моделей.
Что дальше? В перспективе — автоматическая генерация витрин
Наш фреймворк уже закрывает 100% задач по созданию таблиц (DDL) и подготовке хэшей (DML) для ядра Data Vault. Но мы смотрим дальше. Следующий логичный шаг — автоматизация сборки бизнес‑витрин (Business Vault / Information Marts).
Здесь мы уже нащупали алгоритм, который можно и нужно унифицировать. Главная боль при сборке витрин над классическим Data Vault — это необходимость постоянно вычислять срезы актуальности, стыкуя Хабы с множеством Сателлитов по временным интервалам. Если делать это «в лоб» через тяжёлые неэквивалентные JOIN, производительность быстро упадёт.
В перспективе движок будет автоматически сканировать все Сателлиты, привязанные к конкретному бизнес‑ключу Хаба, и генерировать общий пул периодов эффективности (единую хронологическую ленту всех изменений). На основе этого пула будет рассчитываться и наполняться аналог классической Point‑in‑Time (PIT) таблицы. В ней для каждого бизнес‑ключа на каждый уникальный момент времени будут зафиксированы готовые ссылки на актуальные строки из всех Сателлитов.
В итоге аналитику или генератору витрин больше не придётся писать сложные оконные функции. Достаточно будет сделать один простой EQUAL JOIN (=) с PIT‑таблицей, чтобы мгновенно получить срез данных на любую историческую дату. Разумеется, всю эту логику мы точно так же упакуем в метаданные и наш no‑code интерфейс на FastAPI.
Если вам интересно узнать подробнее о том, как реализован конкретный шаг — давайте обсудим в комментариях!

