# Задание 4: `dm.dq_errors_daily` **Чему учит:** у брака свой день и свой срок жизни. Витрина качества живет по времени загрузки, а не по модельному дню, и переживает собственный источник — поэтому «пересобрать все» здесь означает «стереть историю». ## Роль витрины Сводка приемки: сколько строк за день не прошли разбор и по какой причине. Читают ее, когда качество потока просело: сначала смотрят, вырос ли класс брака, потом идут за сырым текстом в саму таблицу ошибок. Витрина отвечает на вопрос «сколько и чего», а не «что именно сломалось». Разбирательство с конкретной строкой начинается в `ods.event_errors_dist` и `ods.order_errors_dist`, где рядом с классом лежат сырой текст и координаты доставки. ## Источник: единственное исключение из правила `_v` Общее правило домашек — читать через публичный договор `_v`. У таблиц брака такого договора нет, и заводить его ради этой витрины не надо: брак — не сущность бизнес-модели, а диагностика слоя приема. Читать нужно распределенные таблицы напрямую: - `ods.event_errors_dist` — брак разбора событий; - `ods.order_errors_dist` — брак разбора слепка заказов. Именно `_dist`, а не `_rep`: локальная таблица вернет строки одного шарда, и витрина тихо покажет половину брака. Ошибки при этом не будет — ошибка появится позже, когда по витрине примут решение. ## Зерно и форма Зерно — **день загрузки × источник × класс брака**. Зерно источника — строка брака, значит зерно сменилось и витрина материализуется: `dm.dq_errors_daily_rep` / `_dist`, донор `_stage_rep` / `_stage_dist`, публичное `dm.dq_errors_daily_v`. Ключ партиции — день загрузки. День здесь — `toDate(_load_ts)`, то есть настоящее время, когда строка приехала в хранилище, а не модельный день мира. Модельного дня у брака нет и быть не может: он живет в содержимом, а содержимое как раз и не разобралось. ## Состав колонок | Колонка | Тип | Что это | |---|---|---| | `load_date` | `Date` | день загрузки, в который приехал брак | | `source` | `LowCardinality(String)` | `event` или `order` | | `error_class` | `LowCardinality(String)` | класс брака из таблицы ошибок | | `error_rows` | `UInt64` | число забракованных строк | | `_load_id` | `String` | запуск, собравший день | | `_load_ts` | `DateTime64(3, 'UTC')` | когда собран | Две похожие колонки рядом различаются намеренно: `load_date` — про день, когда приехал брак, `_load_ts` — про запуск, который собрал витрину. Первая деловая, вторая служебная, и совпадать они не обязаны. Классы у двух источников свои и не пересекаются по смыслу, поэтому колонка `source` в зерне обязательна: без нее одинаковое имя класса у событий и у заказов слилось бы в одну строку. ## Ритм пересборки: витрина переживает источник Таблицы брака живут месяц (`TTL`), сырые данные — трое суток. Витрина живет без срока: сводка «сколько брака было в марте» нужна и после того, как сами строки марта удалены. Отсюда правило прогона: пересобираются **только те дни загрузки, которые еще есть в источнике**. Спроси у источника список живых дней и замени партиции ровно этих дней. Соблазн «собрать все заново в донор и подменить таблицу целиком» здесь стоит дороже обычного: истекшие по `TTL` дни исчезли бы из витрины навсегда, а вместе с ними — единственная оставшаяся память о них. Текущий день загрузки не замерзает до конца суток: поток событий идет, и брак в него еще добавится. Это нормально, день просто пересоберется на следующем прогоне. ## Ключ шардирования Твое решение по [правилу донора](../architecture/storage.md), «Раскладка по шардам». Витрина крошечная: классов брака у событий и заказов вместе меньше десятка. Учти это в защите ключа — иногда правильный ответ звучит как «перекос здесь ничего не стоит, и вот почему». ## Как проверить себя Деловой ключ — пара `source` и `error_class` внутри `load_date`. Область проверки — дни загрузки, которые еще живы хотя бы в одной таблице ошибок: день за границей `TTL` сверять не с чем, и его отсутствие в источнике не ошибка. Собери прямой счет по обеим таблицам ошибок, соедини с `dm.dq_errors_daily_v` полным внешним соединением по ключу и посчитай строки, где ключа нет с одной из сторон или где расходится `error_rows`. Ноль — зеленый. Отдельно: сумма `error_rows` по живым дням равна общему числу строк в `ods.event_errors_dist` плюс `ods.order_errors_dist`. Если эта сумма вышла примерно вдвое меньше — витрина почти наверняка собрана с `_rep` вместо `_dist` и видит один шард из двух. **Пустая витрина — верный ответ.** На чистом стенде брака нет, обе стороны пусты, расхождений ноль, задача зеленая. Проверка, которая требует хотя бы одной строки, на здоровом стенде краснела бы всегда и приучала бы себя игнорировать. ## Приемка - [ ] пять объектов витрины созданы, донор объявлен через `AS` и шардируется ключом цели; - [ ] источники — `_dist`-таблицы обоих видов брака; - [ ] прогон заменяет партиции только живых дней источника и не трогает истекшие; - [ ] задача `dq_errors_daily` в `homework_check` зеленая — в том числе на чистом стенде, где брака нет; - [ ] задача витрины добавлена в `dm_transform`.