diff --git a/docs/architecture/dds/README.md b/docs/architecture/dds/README.md index 6308b32..b447f43 100644 --- a/docs/architecture/dds/README.md +++ b/docs/architecture/dds/README.md @@ -54,10 +54,6 @@ - Наполняет слой [дневной конвейер](../etl.md): запуск, объём прогона и замена партиций на кластере — его устройство. -## Открытые решения - -- Витрины DM — #135. - ## Родословная Дом набора и первая сущность собраны развилкой #130 карты #127; форма diff --git a/docs/architecture/dm.md b/docs/architecture/dm.md new file mode 100644 index 0000000..7371074 --- /dev/null +++ b/docs/architecture/dm.md @@ -0,0 +1,155 @@ +# Слой 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)); +- счётчики классов расхождений против описи мира — по дням с закрытым + окном; +- счётные сверки витрин стенда с их 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. diff --git a/docs/architecture/etl.md b/docs/architecture/etl.md index 41a4d42..4ce9370 100644 --- a/docs/architecture/etl.md +++ b/docs/architecture/etl.md @@ -14,8 +14,8 @@ Дневной конвейер на стенде один — даг `etl_pipeline`. Своей работы у него нет: он дёргает слойные даги последовательно и ждёт каждый — -`dds_transform`, затем `dm_transform`. Контур DQ этапа 4 встанет его же -звеном после DM; состав звена — предмет развилки витрин (#135) и нарезки. +`dds_transform`, затем `dm_transform`. Контур DQ этапа 4 встаёт его же +звеном после DM; состав звена — в [доке слоя DM](dm.md). Слойные даги устроены по образцу `orders_ingest`: без расписания, один активный прогон, запускаются триггером конвейера или рукой при разборе @@ -42,7 +42,8 @@ Параметров у конвейера нет: объём он выводит сам из `world_position` и состояния слоёв. Сессии догоняют хвост — от последнего собранного дня до последнего прожитого; заказы пересобирают дышащее окно K от позиции; карта -идентичностей пересчитывается целиком; правила витрин — у витрин (#135). +идентичностей пересчитывается целиком; правила витрин — у +[слоя DM](dm.md). Поэтому один и тот же запуск одинаково верен из триггера, с кнопки менти и после падения, а разгону на семь дней хватает одного прогона. @@ -62,7 +63,9 @@ вставка через Distributed раскладывает строки по шардам, — затем `REPLACE PARTITION` на кластере атомарно подменяет кусок каждого шарда. Эта механика — часть самих трансформаций и строится этапом 4: без неё дневной -прогон не повторяем. +прогон не повторяем. Сущности, пересчитываемые целиком, идемпотентны и без +замены: полная перезапись в `ReplacingMergeTree` — так живут карта +идентичностей и витрина `daily_traffic` ([слой DM](dm.md)). ## Не входит @@ -70,7 +73,8 @@ диапазон, включая форму необязательных параметров того же дага, — этап 5 (#7); туда же стык #111 про дату модельного мира и `logical_date`. - Инкрементальный ETL вместо пересборки — техдолг #2. -- Состав витрин `dm_transform` и звена DQ — #135 и нарезка этапа 4. +- Состав витрин `dm_transform` и звена DQ — [дока слоя DM](dm.md); + имена задач — нарезка этапа 4. ## Отклонённые варианты