# Слой 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.