- Зачем:
- менти строит витрины 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
Задание 2: dm.top_pages_daily
Чему учит: зерно сменилось — значит витрина материализуется, живет
дневными партициями и встает в конвейер. Первая настоящая витрина: пять
объектов, донор, замена партиции, задача в dm_transform.
Роль витрины
Популярность страниц по дням: сколько раз страницу открыли, сколько разных посетителей ее видели и что на ней делали кроме просмотра.
Торговое событие садится на ту страницу, где случилось: корзина — на карточку товара, покупка — на страницу подтверждения заказа. Поэтому у карточки товара рядом стоят просмотры и корзины, и их отношение — конверсия карточки — главный вопрос к этой витрине: какие товары кладут в корзину, едва открыв, а какие смотрят и уходят.
Воронки «просмотр → корзина → покупка» здесь нет и быть не может: корзина и покупка живут на разных страницах, и связать их шаги в одну цепочку на зерне страницы не из чего. Путь одного человека собирают из событий или из сессий, а витрина страниц отвечает про страницу.
Зерно и форма
Зерно — день события × страница. Источник — события, у него зерно
события, значит зерно сменилось и витрина материализуется:
dm.top_pages_daily_rep / _dist, донор _stage_rep / _stage_dist,
публичное dm.top_pages_daily_v.
Страница — это page_path, путь url без домена и параметров запроса;
считается он здесь же, из колонки url источника. Полный адрес в зерно не
берется: с параметрами запроса одна и та же страница рассыпалась бы на
сотни строк, и витрина перестала бы отвечать на свой вопрос.
Ключ партиции — день: он же единица пересборки.
Источник — dds.event_v, публичный договор события. Витрина задания 1
источником не служит: витрина слоя DM читает договор DDS, как и стендовые
витрины, и не зависит от того, выполнена ли соседняя домашка.
Состав колонок
| Колонка | Тип | Что это |
|---|---|---|
report_date |
Date |
день события |
page_path |
LowCardinality(String) |
путь страницы |
pageviews |
UInt64 |
число событий pageview на странице |
visitors |
UInt64 |
число разных client_id на странице |
cart_adds |
UInt64 |
число событий add_to_cart на странице |
purchases |
UInt64 |
число событий purchase на странице |
_load_id |
String |
запуск, собравший день |
_load_ts |
DateTime64(3, 'UTC') |
когда собран |
Счетчики условные: строку дают все три типа событий, а различает их
countIf. Отфильтровать источник по event_type = 'pageview', как
подсказывает имя витрины, — значит получить витрину из одних просмотров:
страницы останутся на месте, а cart_adds и purchases у всех строк
обнулятся. Ошибка тихая: витрина выглядит собранной, и главный ее вопрос —
конверсия карточки — молча отвечает «ноль везде».
Ритм пересборки
Источник — события: день замерзает сразу, как только прожит. Значит пересобирать нужно только то, что еще не собрано: хвост дней, которых в витрине нет, — от первого несобранного прожитого дня до последнего прожитого. Готовые дни трогать незачем, а вот дыру, оставленную частичным отказом, объем прогона обязан подобрать: ищи не последний собранный день, а первый несобранный.
Механика — сборка отрезка в донор одной вставкой и замена дневных партиций
по одной, как у revenue_daily.
Ключ шардирования
Твое решение по правилу донора, «Раскладка по
шардам». Подумай, чем эта витрина отличается от revenue_daily: там кроме
дня было одно измерение из шести близких по объему значений, здесь —
страницы, которых много и которые по популярности очень неровные.
Как проверить себя
Деловой ключ — page_path внутри дня. Собери прямой агрегат из
dds.event_v за день, соедини с dm.top_pages_daily_v полным внешним
соединением по ключу и посчитай строки, где ключа нет с одной из сторон или
где расходится любой из четырех счетчиков. Ноль — зеленый.
Две проверки попроще, ими удобно ловить грубую ошибку до полного соединения:
- сумма
pageviewsза день равна числу событийpageviewэтого дня вdds.event_v; - сумма
purchasesза день равна числу событийpurchaseэтого дня.
Проверки ловят разное. Фильтр WHERE event_type = 'pageview' первая из них
пропускает: отбор строк по типу не меняет счет событий того же типа.
Расхождение покажет только вторая — вместе с cart_adds обнулятся и
purchases. Проверку выбирают по тому, умеет ли она покраснеть на твоей
ошибке, а не по тому, как быстро она пишется: проверка, которая не умеет
краснеть, бесполезна.
visitors складывать по строкам нельзя: один посетитель ходит по разным
страницам и в сумме посчитается несколько раз. Число посетителей дня
спрашивают у dm.daily_traffic_v, а не у этой витрины.
Приемка
- пять объектов витрины созданы, донор объявлен через
ASи шардируется ключом цели; dm.top_pages_daily_vперечисляет колонки явно, включая_load_idи_load_ts;- объем прогона выводится из состояния витрины, а не задается руками;
- задача
top_pages_dailyвhomework_checkзеленая; - задача витрины добавлена в
dm_transform, повторный прогон конвейера результат не меняет.