Files
clickstream-data-platform/docs/labs/top-pages-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

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, повторный прогон конвейера результат не меняет.