Привет, Хабр! Меня зовут Алексей Миронов, я главный разработчик отдела разработки хранилищ данныз в «Газпром ЦПС». Сегодня я расскажу о своем опыте внедрения 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| -- если изменить порядок ключей, то получим другой hkeyselect md5( lower( 'Датаинженер'||'^'|| 'Постгрессов Констрэйнтин Вакуумович' ))md5 |--------------------------------+8344090c20bb1b420c2856cd7ba4bddb|
Мы создали кастомный составной тип данных, который хранит имя ключа, его значение и порядковый номер. А затем написали две функции, принимающие на вход массив этих атрибутов:
· Сборка эталонной строки изменений. Функция разворачивает входящий массив в плоскую структуру. Внутри агрегатора происходит жёсткая сортировка элементов по порядковому номеру и имени. Это самое главное: в каком бы порядке атрибуты ни пришли на вход, строка изменений всегда соберётся в строго детерминированной последовательности. Все значения очищаются от пробелов, приводятся к нижнему регистру и защищаются от NULL-значений, склеиваясь через разделитель ^.
· Генерация эталонного UUID. Использует ту же логику сортировки, но на финальном шаге оборачивает полученную эталонную строку в алгоритм хэширования, возвращая компактный и чистый UUID.
selectmeta.make_hash_key( array[ row(0,'position', 'Датаинженер')::hash_data, row(0,'fio', 'Постгрессов Констрэйнтин Вакуумович')::hash_data ])make_hash_key |------------------------------------+0d6e2519-5d52-9dca-6723-ed5511dd84ed| -- теперь если меняем порядок ключей – hkey не изменяетсяselectmeta.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.
Если вам интересно узнать подробнее о том, как реализован конкретный шаг — давайте обсудим в комментариях!
ссылка на оригинал статьи https://habr.com/ru/articles/1079926/