- Зачем:
- менти строит витрины 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>
115 lines
8.3 KiB
Markdown
115 lines
8.3 KiB
Markdown
# Задание 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`.
|
||
|
||
## Ключ шардирования
|
||
|
||
Твое решение по [правилу донора](../architecture/storage.md), «Раскладка по
|
||
шардам». Подумай, чем эта витрина отличается от `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`. Проверку выбирают по тому, умеет ли она покраснеть на твоей
|
||
ошибке, а не по тому, как быстро она пишется: [проверка, которая не умеет
|
||
краснеть, бесполезна](../architecture/testing.md).
|
||
|
||
`visitors` складывать по строкам нельзя: один посетитель ходит по разным
|
||
страницам и в сумме посчитается несколько раз. Число посетителей дня
|
||
спрашивают у `dm.daily_traffic_v`, а не у этой витрины.
|
||
|
||
## Приемка
|
||
|
||
- [ ] пять объектов витрины созданы, донор объявлен через `AS` и шардируется
|
||
ключом цели;
|
||
- [ ] `dm.top_pages_daily_v` перечисляет колонки явно, включая `_load_id` и
|
||
`_load_ts`;
|
||
- [ ] объем прогона выводится из состояния витрины, а не задается руками;
|
||
- [ ] задача `top_pages_daily` в `homework_check` зеленая;
|
||
- [ ] задача витрины добавлена в `dm_transform`, повторный прогон конвейера
|
||
результат не меняет.
|