Модуль 17. Каталог, линейдж и документация как код
Цель модуля — так устроить знания о витринах, чтобы на вопрос «где правда?» можно было отвечать за секунды: у каждой метрики есть владелец, версия, календарь/валюта, источники/зависимости видны на графе линейджа, а документация всегда совпадает с тем, что в проде.
Картина мира: что значит «документация как код»
Документация как код = артефакты (описания метрик, таблиц, вьюх, тестов DQ, SLO) лежат в Git рядом с SQL, проверяются линтерами в CI и собираются автоматически в статический сайт/каталог после каждого PR.
Базовые кирпичики:
- Паспорта метрик (YAML) — «истина» про формулу/календарь/валюту/владельца/версию.
- Комментарий в SQL (комментарии в DDL/VIEW + COMMENT ON COLUMN/comment в CH) — краткая подсказка «что это».
- Парсер SQL → lineage — вытягиваем «откуда берутся поля» и строим граф.
- Генератор сайта — mkdocs/docusaurus (или «тяжёлый» каталог типа OpenMetadata/DataHub).
- PR → превью — каждый PR обновляет доки/lineage и выкатывает ссылку-превью.
Что выбрать: лёгкий стек vs «каталог-как-платформа»
Вариант A. Лёгкий стек (быстрый старт)
- Хранение знаний: /metrics/*.yaml, комментарии в sql/views/*.sql.
- Генерация: Python-скрипт собирает Markdown + lineage.json.
- Сайт: MkDocs (Material) или Docusaurus, деплой в GitHub Pages/Netlify.
- Плюс: быстро, дёшево, под полным контролем.
- Минус: меньше «готовых интеграций».
Вариант B. Каталог-платформа (OpenMetadata / DataHub / dbt-docs)
- OpenMetadata/DataHub — готовые UI, политики, глоссарий, линиидж, алерты, ingestion-коннекторы (в т.ч. dbt).
- dbt-docs — если вы ведёте слой семантики в dbt, можно раздавать manifest.json/catalog.json и строить сайт автоматически; DataHub/OM умеют их забирать.
Практика модуля покажет оба пути: начнём с лёгкого, опишем «хэндшейк» с тяжёлым.
Структура репозитория «знаний»
repo/
sql/
tables/ # DDL таблиц (CH)
views/ # CREATE OR REPLACE VIEW vw_*.sql (семантика)
metrics/ # YAML-паспорта метрик
RETAIL_NET_SALES_DAY.yaml
RETAIL_CR_MINUTE.yaml
tests/ # DQ/регрессы (SQL/YAML)
docs/ # автогенерируемые md-файлы (не редактировать руками)
lineage/ # lineage.json + картинки/экспорты
ci/
docs_build.py # генератор доков
lineage_extract.py # парсер SQL → lineage
validate_metrics.py # бот-проверки YAML и SQL-комментариев
mkdocs.yml # конфиг сайта
Паспорта метрик: единый шаблон (YAML)
Минимальный шаблон (пример для RETAIL_NET_SALES_DAY):
id: RETAIL_NET_SALES_DAY
title: Net Sales per Day
version: 2
owner: retail_analytics@company.com
domain: retail
grain: [day, shop_id, category_id]
calendar: gregorian # или 4-5-4
currency: operation_date # или report_date
source_view: vw_retail_daily
definition_sql: |
net_sales_base = sumMerge(gross_state) - sumMerge(refund_state)
acceptance:
freshness_sla_minutes: 60
balance_vs_core_rel: 0.002 # ≤0.2%
dq_invariants:
- "refunds_base >= 0"
- "aov >= 0"
retro_window_days: 30
changelog:
- version: 2
date: 2025-07-01
reason: "Добавлен учёт промо-скидок"
- version: 1
date: 2025-05-22
reason: "Начальная публикация"
Правила:
- owner обязателен (e-mail/группа).
- calendar/currency обязательны, если метрика денежная и/или есть финкалендарь.
- retro_window_days влияет на nightly пересборку (см. М14).
- Любая смена формулы → новая версия + запись в changelog.
Комментарии в SQL и «самодокументирующиеся» VIEW
В ClickHouse удобно оставлять комментарии в DDL (включая COMMENT в CREATE TABLE/CREATE VIEW через COMMENT в SETTINGS для полей). Если поддерживать COMMENT ON COLUMN недоступно, держите пролог вьюхи в виде многострочного комментария.
Шаблон вьюхи с прологом:
/* view: vw_retail_daily title: Daily Retail Metrics owner: retail_analytics@company.com metrics: [RETAIL_NET_SALES_DAY, RETAIL_AOV_DAY] grain: [day, shop_id, category_id] notes: "Валюта на дату операции; см. dict_fx" */ CREATE OR REPLACE VIEW db_sem.vw_retail_daily AS SELECT day, shop_id, category_id, sumMerge(gross_state) AS gross_sales_base, sumMerge(refund_state) AS refunds_base, (gross_sales_base - refunds_base) AS net_sales_base, sumMerge(qty_state) AS qty, uniqCombinedMerge(buyers_state) AS buyers, net_sales_base / NULLIF(qty, 0) AS aov FROM db_marts.agg_sales_daily_state GROUP BY day, shop_id, category_id;
Генератор доков прочитает этот пролог и свяжет вьюху с YAML-метриками.
Извлечение линейджа (SQL → граф)
Задача: понять зависимости: vw_* → agg_*_state → mart_* → core.*.
Источники:
- sql/views/*.sql — парсим SELECT ... FROM ... JOIN ... и CTE.
- sql/tables/*.sql — для материализованных представлений (CREATE MATERIALIZED VIEW ... TO ... AS SELECT ...).
- (опционально) system.query_log — для фактических трасс (кто реально читал что).
Простой формат lineage.json:
{
"nodes": [
{"id": "db_sem.vw_retail_daily", "type": "view"},
{"id": "db_marts.agg_sales_daily_state", "type": "table"},
{"id": "core.sales", "type": "table"}
],
"edges": [
{"from": "db_marts.agg_sales_daily_state", "to": "db_sem.vw_retail_daily", "kind": "select"},
{"from": "core.sales", "to": "db_marts.agg_sales_daily_state", "kind": "materialize"}
]
}
Стратегия парсинга:
- Используйте SQL-парсер (например, sqlglot) в генераторе, либо аккуратный разбор FROM/JOIN/WITH регулярками (для CH-диалекта).
- Игнорируйте субзапросы, где нет внешних таблиц.
- Для MATERIALIZED VIEW … TO target AS SELECT … создайте ребро source → target (materialize).
- Для DICTIONARY — узел типа dict.
В «тяжёлом» варианте (OpenMetadata/DataHub) — запускаем их ingestion пайплайн для CH/dbt: он сам построит lineage и свяжет с dbt-артефактами.
Генерация статического сайта
MkDocs (Material) — быстрый вариант
mkdocs.yml:
site_name: Data Marts Docs
nav:
- Метрики:
- Обзор: docs/metrics/index.md
- Retail: docs/metrics/retail.md
- Вьюхи: docs/views/index.md
- Таблицы: docs/tables/index.md
- Линейдж: docs/lineage/index.md
theme:
name: material
features: [navigation.instant, content.tabs.link]
markdown_extensions:
- toc:
permalink: true
Генератор ci/docs_build.py делает:
- читает metrics/*.yaml → генерит docs/metrics/*.md (карточки метрик);
- собирает описания из прологов views/*.sql → docs/views/*.md;
- парсит lineage → lineage/lineage.json, рендерит SVG/PNG (через Graphviz или mermaid) → docs/lineage/index.md;
- подтягивает DQ-статус (из таблиц dq_results/sem_meta) → красно/зелёные индикаторы свежести/баланса.
Превью в PR (GitHub Actions пример)
name: Docs
on:
pull_request:
branches: [ main ]
jobs:
build-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- run: pip install mkdocs-material sqlglot pyyaml graphviz
- run: python ci/validate_metrics.py
- run: python ci/lineage_extract.py
- run: python ci/docs_build.py
- run: mkdocs build --strict
- name: Upload preview artifact
uses: actions/upload-pages-artifact@v3
with: { path: 'site' }
deploy-preview:
needs: build-docs
permissions: { pages: write, id-token: write }
runs-on: ubuntu-latest
steps:
- uses: actions/deploy-pages@v4
Бот добавляет ссылку «Preview docs» в PR. При мерже — pages обновляются.
«Боты-проверки»: не даём влить «сирот»
Файл ci/validate_metrics.py проверяет:
- Каждый новый *.yaml имеет поля: id, version, owner, grain, calendar (если денежная — и currency).
- Каждая вьюха в sql/views/*.sql имеет пролог-комментарий со владельцем и ссылкой на метрики.
- Если изменился views/*.sql, но не увеличилась version метрики — Fail.
- Каждый owner соответствует известной группе/почте (список в owners.json).
- Столбцы с PII в vw_* помечены/маскированы (эвристика по списку pii_fields.json).
Политика: no owner → no merge. Любая вьюха без владельца/паспорта метрики = «красный PR».
Как связать DQ/Observability с документацией
Пусть у нас есть таблицы из М5/М10:
CREATE TABLE sem_meta (view_name String, updated_at DateTime) ENGINE=MergeTree ORDER BY view_name; CREATE TABLE dq_results ( test_name String, scope String, ts DateTime, status LowCardinality(String), value Float64, threshold Float64, details String ) ENGINE=MergeTree ORDER BY (test_name, ts);
Генератор доков делает:
- для каждой вьюхи подтягивает updated_at и SLA (freshness_sla_minutes из YAML) → индикатор «Свежесть: зелёная/жёлтая/красная»;
- для каждой метрики рисует виджет «Баланс vs CORE/GL за вчера/7д/30д» (последний статус из dq_results).
Эффект: документация не «про текст», а про живое состояние.
OpenMetadata/DataHub/dbt-docs: когда и как подключать
OpenMetadata
- Поднимаем сервис, настраиваем ingestion-пайплайн для ClickHouse (или через JDBC/ICEBERG источники).
- Добавляем ingestion dbt-артефактов (если используете dbt-clickhouse): manifest.json/catalog.json.
- Выгружаем владельцев/термины глоссария; настраиваем алерты по свежести/тестам.
DataHub
- Аналогично: инжест CH, dbt, BI источники (Looker/Tableau), глоссарий, поля PII, политики.
dbt-docs
- Если слой семантики живёт в dbt → просто публикуем dbt docs serve/build; DataHub/OM умеют забирать lineage из manifest.json.
Комбинация: продолжаем хранить YAML метрик и прологи в Git → генератор MkDocs создаёт статический сайт «для инженеров», а платформа-каталог — «единое окно» для бизнеса, безопасности и платформы.
Практика: собираем каталог по vw_*
Шаги
- Добавьте прологи в sql/views/*.sql и заполните metrics/*.yaml.
- Запустите python ci/lineage_extract.py → получите lineage/lineage.json.
- Запустите python ci/docs_build.py → получите site/.
- Откройте превью, пройдите чек-лист (ниже).
- Подключите OpenMetadata/DataHub ingestion (по желанию): проверьте соответствие владельцев/глоссария.
Что выдаём
- Статический сайт: карточки метрик, страницы вьюх/таблиц, граф линейджа, индикаторы DQ/SLA, глоссарий терминов.
- lineage.json: пригоден для экспорта в BI/каталог.
- Чек-листы и линтеры в CI.
Чек-лист наполнения паспорта и публикации
На метрику:
- owner указан (группа, не «ник»).
- version актуальна, есть changelog.
- grain (зерно), calendar, currency (если применимо).
- source_view (vw_*), definition_sql.
- SLA: freshness_sla_minutes, допуски (balance_vs_core_rel).
- retro_window_days определён.
На вьюху:
- Пролог с owner, metrics, grain, notes.
- Чтение без FINAL, без SELECT *.
- Фильтры по времени/партиции соответствуют ORDER BY.
- Поля PII скрыты/маскированы (если нужен публичный источник).
- В каталоге отображается свежесть и DQ.
Репозиторий/CI:
- Бот-проверки (no-owner → no-merge).
- Автогенерация доков и превью в PR.
- Сборка lineage и экспорт.
- Линтер SQL (запрет FINAL, SELECT *, enforce time filter).
Кейсы из практики
Кейс 1. «У нас три разных Net Sales»
Симптом: разные отчёты показывают разные цифры «нетто».
Разбор: в одном отчёте валюта «на дату отчёта», в другом — «на дату операции»; в третьем не учтены возвраты «задним числом».
Фикс: два разных YAML (операция/отчёт), разные VIEW; version bump + changelog; DQ «баланс vs CORE» включен. В каталоге чётко видно, какая метрика где используется.
Кейс 2. «Сломали вьюху — BI упал»
Симптом: после PR «изменилась схема», би-коннектор падает.
Разбор: изменили vw_* без обновления паспорта/версии.
Фикс: бот в CI: изменение views/*.sql требует либо version++ в связанном YAML, либо флаг «схема неизменна». Без этого — no-merge.
Кейс 3. «Нет владельца — нет починки»
Симптом: DQ красный, неясно, кто чинит.
Фикс: правило — у каждой вьюхи/метрики есть владелец; без него PR не мёржится; в каталоге видны RACI (Owner / Tech / Data Steward).
Риски и как их погасить
|
Риск |
Проявление |
Митигация |
|---|---|---|
|
«Сироты» без владельца |
Никто не чинит инциденты, метрика «висит» |
Бот-проверка owner (no-owner → no-merge), RACI-таблица |
|
Дрейф доков |
Док-сайт «про одно», прод — «про другое» |
Генерация доков из исходников (YAML/SQL), превью на PR, запрет ручных правок docs/ |
|
Неполный линейдж |
Не видно скрытых зависимостей |
Парсинг SQL + ingestion из dbt/OpenMetadata/DataHub; периодическая сверка с query_log |
|
Смешение календарей/валют |
«Не бьются» цифры между командами |
Поля calendar/currency — обязательны; разные VIEW для разных режимов; changelog |
|
Утечка PII в каталоге |
Публикация чувствительной информации |
Маскирование отображаемых колонок; RLS; каталог — только агрегаты/описания, без данных |
|
Сложный онбординг |
Команды не наполняют YAML |
Шаблоны (cookiecutter), линтер подсказывает, сайт-«пустышка» показывает пробелы (красные бейджи) |
Советы по внедрению (чтобы «завелось»)
- Начните с малого: 10–20 ключевых метрик и 10–20 vw_*. Сразу подключите бота «no-owner → no-merge».
- Покажите ценность: вынесите в BI ссылки на карточки метрик в каталоге (иконка «ℹ» рядом с полем).
- Интегрируйте DQ: зелёный/красный индикатор в карточке метрики повышает доверие.
- Дальше — платформа: когда артефактов станет сотни, поднимите OpenMetadata/DataHub и подключите ingestion, сохранив «док как код» для экспертов.
Что отдаём по модулю (артефакты)
- Статический сайт (MkDocs) с карточками метрик, страницами вьюх/таблиц, линейджем, DQ/SLA-индикаторами.
- lineage.json/граф (mermaid/graphviz) для импорта/BI-ссылок.
- Шаблоны: metrics/*.yaml, пролог вьюхи, глоссарий, owners.json.
- Скрипты: lineage_extract.py, docs_build.py, validate_metrics.py.
- CI-конфиги: сборка/превью, линтеры, fail-политики.
- Чек-лист наполнения паспорта и публикации.
Итог
Каталог, линейдж и документация как код делают слой витрин прозрачным и управляемым: любой сотрудник видит, где считать, кто владелец, какая формула, какая свежесть/DQ, и откуда тянутся данные.
С этим подходом «разные правды» становятся исключением, а изменения — контролируемыми: PR → проверка → превью → релиз.
Arenadata QuickMarts (ADQM) — корпоративная платформа на базе ClickHouse для быстрого слоя витрин и near-real-time аналитики. Решает задачи «быстрых» дашбордов и API с низкой латентностью и высокой конкуррентностью, работает поверх вашего DWH/лейкхауса как serving-уровень. Даёт предсказуемую производительность на терабайтно-петабайтных объёмах за счёт колоночного хранения, компрессии и предагрегатов (Materialized Views, AggregatingMergeTree), подключается к Kafka/S3 и стандартным BI-инструментам по SQL/HTTP. Для корпоративных ИТ ADQM предлагает поддержку и SLA, отказоустойчивые кластеры (HA/DR), безопасность (RBAC, LDAP/OIDC, шифрование трафика и данных), мониторинг и резервное копирование. Платформа хорошо ложится на методологию курса: семантика vw_*, роллап-слои, NRT-ингест, SLO/наблюдаемость и «гвардейки» для BI/API. Итог — быстрый запуск витрин за недели, снижённые риски в проде и предсказуемая стоимость владения.



