Files
clickstream-data-platform/docs/architecture/dm.md
T
ddadmin 9ea361d199 feat(dds): добавлен публичный контракт события
Зачем:
- Показать слой DDS как договор данных, которому не нужна физическая копия.
- Сохранить простой учебный стенд без отдельного маршрута ради узких прав.

Что:
- Добавлено кластерное представление dds.event_v с явными 44 колонками.
- Роли bi_reader разрешено чтение ODS, DDS и DM при договоре чтения через _v.
- Согласованы мастер-спека, ADR, справочники архитектуры и живые тикеты этапа.
- Зафиксировано исследование поведения DEFINER поверх Distributed.

Проверка:
- make config-test
- make smoke
- make check-clickhouse
- Приёмочные запросы: равенство ODS/DDS, состав колонок, коды устройств и права bi.
2026-08-25 17:13:35 -04:00

160 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Слой DM: витрины
Как модель DDS превращается в витрины для потребителя BI: какую форму
принимает витрина, кто какие витрины строит и как слой проверяется. Состав
колонок каждой витрины задаёт раздел 7 мастер-спеки; здесь — форма,
наполнение и контроль.
## Форма: по зерну, не по цене запроса
Витрина, меняющая зерно источника — дневной агрегат, сверка по заказам, —
материализуется: `dm.<имя>_rep` / `_dist` и публичное `dm.<имя>_v` с явным
списком колонок, по общим [конвенциям хранилища](storage.md). Наполняет её
`dm_transform`; ритм и механика пересборки — ниже. Витрина, сохраняющая зерно
источника — переименование, обогащение строки, — остаётся обычным
представлением: копию событий ради других имён колонок не держит ни один
боевой склад.
Правило выбрано учебной целью, а не ценой запроса. На объёмах стенда
представлениями обошлось бы всё: условия сквозь цепочку представлений
проходят ([исследование](../research/2026-08-22-view-predicate-pushdown.md)).
Но пересборка агрегатов конвейером — производственный паттерн, ради
которого стенд и строится, а пара «таблица рядом с представлением»
показывает: форма — решение по роли витрины, у одного слоя законны обе.
## Кто строит: стенд и менти
Стенд привозит готовыми витрины трёх сюжетов дашборда этапа 8 и
инфраструктуру контроля:
- `revenue_daily` — выручка по дням и категориям; образец материализованной
витрины, на него ссылаются учебные задания;
- `purchase_vs_orders` — сверка A+C с классами расхождений; зерно — заказ,
шардирование сохраняет ко-локацию заказов по `cityHash64(order_id)`;
- `daily_traffic` — пара «посетители» / «известные пользователи», локальное
обогащение [картой идентичностей](dds/identity.md);
- `dq_summary` — сводка проверок качества (ниже).
Остальные витрины спеки — учебные задания: менти строит их сам по ТЗ с
образцом. Разминка — представление `events_enriched_v` (зерно события),
затем агрегаты `top_pages_daily`, `session_overview` и `dq_errors_daily`,
последней — самая содержательная `utm_effectiveness` с уроком `declared_*`.
Построенная витрина встаёт в конвейер руками менти: финальный пункт
каждого ТЗ — добавить задачу своей витрины в `dm_transform`. До этого
конвейер о ней не знает и остаётся зелёным, после — витрина пересобирается
наравне со стендовыми.
Дашборд этапа 8 стоит только на витринах стенда: приёмка этапа не зависит
от выполненных домашек. Расширение дашборда визуализациями по своим
витринам — отдельная домашка после этапа 8.
## Ритм пересборки
День витрины пересобирается, пока может измениться его источник, —
поэтому ритмов три, по числу источников:
- **от событий и сессий** — день замерзает сразу; пересборка догоняет
хвост несобранных дней заменой дневных партиций;
- **от заказов** — день дышит окном K и закрывается; пересборка заменяет
партиции окна, дни за ним закрыты по построению;
- **от карты идентичностей** — не замерзает никогда: склейка
ретроспективна, покупка сегодня делает куку известной во всех её прошлых
днях, а карта пересчитывается целиком.
Поэтому `daily_traffic` наследует у своего источника и ритм, и механику:
полный пересчёт всех дней каждый прогон в `ReplacingMergeTree(_load_ts)`,
как у самой карты. Дневных партиций у неё нет: партиция — единица замены,
а эта витрина заменяется только целиком. Пересчёт дешёв:
витрина читает сессии и карту локальным соединением по ключу ко-локации.
Пересчитывать точечно только затронутые дни не из чего: для этого надо
знать, какие связки новые, а полный пересчёт карты этот след затирает —
восстановление следа оставлено лабе повышенной сложности (issue #149).
## `dm.dq_summary`
Зерно — «день данных × проверка»: строка отвечает, каково качество дня
сейчас, а не сколько раз его проверяли, — история прогонов и так живёт в
Airflow. Партиция — день данных; прогон звена DQ заменяет партиции
проверенных дней через `REPLACE PARTITION`. Обещанный спекой перевод с
`TRUNCATE+INSERT` выполняется той же механикой замены, что и у витрин, —
ничего отдельного учить не надо.
## Звено DQ конвейера
Встаёт в `etl_pipeline` после DM ([конвейер](etl.md)) и проверяет только
объекты стенда:
- счётная сверка сессий с эталоном `VisitID` — кука × день, эталон читается
из `ods.event_v` ([модель сессии](dds/session.md));
- счётчики классов расхождений против описи мира — по дням с закрытым
окном. Проверка техническая, не урок DQ: её оракул — опись генератора,
а у настоящего магазина оракула нет. Обещание узкое и честное: до
витрин доехал тот мир, который родился, — одна растяжка через весь
путь вместо сторожей на каждом шаге. Верно ли сам мир рождён, она
не видит: опись считает тот же генератор;
- счётные сверки витрин стенда с их DDS-источниками.
Результаты ложатся в `dm.dq_summary`. Звено — не только сторож конвейера,
но и экспонат: менти видит, как контроль качества устроен в бою — по
расписанию, с историей, рядом с трансформациями.
## Даг самопроверки
Домашки контролирует отдельный даг без расписания: менти запускает его
рукой. Задача витрины красная, пока витрины нет или счёт не сходится, и
зелёная, когда построено верно; сообщение об ошибке говорит, что не так и
куда смотреть. Проверки — инварианты против DDS-источников, золотые
константы не прибиваются. В `dq_summary` даг не пишет: он инструмент лабы,
а не история качества конвейера.
Приём проверен на соседнем учебном стенде Airflow + Greenplum:
валидационный даг с группами проверок по слоям давал менти обратную связь
по каждому шагу домашки, не трогая рабочий конвейер.
## Остаётся нарезке
- Имена задач `dm_transform`, звена DQ и дага самопроверки.
- Ключи шардирования остальных материализованных витрин — вместе с их
DDL; правило партиции-донора — в [доке хранилища](storage.md).
- Тексты учебных заданий и точный список проверок обоих контуров.
- Механика сверки A+C в SQL — рамка в спеке, разделы 4 и 7.
## Отклонённые варианты
- **Весь слой представлениями.** Дёшево и всегда свежо, но главный
производственный паттерн слоя — пересборка агрегатов — остался бы
невидим, а `dm_transform` — без работы.
- **Материализовать всё.** Копия событий ради переименования колонок —
антипаттерн, который менти увёз бы в бой.
- **Гибрид по цене запроса.** Выбор формы по стоимости, которой на объёмах
стенда нет: сторож без настоящей опасности.
- **Проверки домашек в конвейерном звене DQ.** Конвейер стоял бы красным до
выполнения лаб, а проверка, которая всегда красная, приучает себя
игнорировать.
- **«Жёлтый» статус недостроенной витрины.** У задачи Airflow нет такого
состояния; изображать его логами — семантика, которую пришлось бы
расшифровывать. Красное «ещё не построено» честнее.
- **Самопроверка пишет в `dq_summary`.** Попытки менти смешались бы с
историей качества конвейера.
- **Окно K для `daily_traffic` — прошлое замерзает.** Дни за окном хранили
бы число известных на момент закрытия — витрина тихо расходится с картой
и протаскивает обратно «знание на дату», отвергнутое развилкой карты
(#133).
- **Инкрементальный пересчёт затронутых дней по `first_matched_at`.**
Производственный ход, но на объёмах стенда его экономия неизмерима, а
след новых связок требует вернуть колонку, выкинутую в #133. Стал лабой
повышенной сложности — issue #149.
- **Superset поверх DDS без слоя DM.** Терялся бы урок «витрина — контракт
под потребителя», против раздела 7 спеки.
## Связанные решения
- [Мастер-спека](../specs/2026-07-30-stand-v2-realism.md), разделы 4 и 7, —
состав колонок витрин и классы сверки.
- [Дневной конвейер](etl.md) — запуск `dm_transform`, объём прогона,
замена партиций на кластере.
- [Дока хранилища](storage.md) — имена объектов, конвенция чтения через `_v`,
партиция-донор.
- [Карта модели DDS](dds/README.md) — источники витрин.
- Резолюция — тикет #135 карты #127.