Files
clickstream-data-platform/docs/labs/dq-errors-daily.md
T
ddadminandClaude Fable 5 f33d75113d feat(dm): добавлены учебные задания витрин и даг самопроверки
- Зачем:
  - менти строит витрины DM сам, от DDL по правилу зерна до задачи в
    конвейере; стенду нужны ТЗ с образцом и обратная связь по каждой
    витрине (#159, резолюция #135).
- Что:
  - docs/labs/: README и пять ТЗ — events_enriched_v, top_pages_daily,
    session_overview, dq_errors_daily, utm_effectiveness; каждое ТЗ
    называет урок, состав колонок, форму по зерну и самопроверку.
  - dags/homework_check.py: ручной даг, задача на витрину, красная без
    витрины или при расхождении; проверки — шесть файлов sql/dq/ с
    инвариантами против DDS и ODS, без золотых констант; в dq_summary
    не пишет.
  - docs: dm.md называет homework_check и снимает «Остается нарезке»,
    §7 и §10 мастер-спеки согласованы с лабами, dds/session.md указывает
    на ТЗ, AGENTS.md пополнен правилом имен docs/labs/.
- Проверка:
  - make lint; make smoke 21/21, make check-clickhouse 10/10,
    make check-services 7/7.
  - на свежем стенде все пять задач homework_check красные и называют
    ТЗ; черновик каждой витрины по тексту ТЗ зеленит свою задачу,
    после сноса — снова красная; строк от самопроверки в dq_summary нет.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 17:44:15 -04:00

119 lines
8.3 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.
# Задание 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`.