- Зачем:
- менти строит витрины 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>
8.3 KiB
Задание 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 дни исчезли
бы из витрины навсегда, а вместе с ними — единственная оставшаяся память о
них.
Текущий день загрузки не замерзает до конца суток: поток событий идет, и брак в него еще добавится. Это нормально, день просто пересоберется на следующем прогоне.
Ключ шардирования
Твое решение по правилу донора, «Раскладка по шардам». Витрина крошечная: классов брака у событий и заказов вместе меньше десятка. Учти это в защите ключа — иногда правильный ответ звучит как «перекос здесь ничего не стоит, и вот почему».
Как проверить себя
Деловой ключ — пара 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.