docs(labs): переписана проза пяти ТЗ ступени 1 и раздел «Как принято»
- Зачем:
- владелец при чтении забраковал прозу ТЗ ступени 1: образы вместо
утверждений, заголовки-метафоры, договоренности стенда поданы как
законы. Тикет #228.
- Что:
- пять ТЗ (задания 1–5) написаны заново по форме ТЗ ступени 2:
содержание, колонки, формулы, самопроверка и приемка сохранены.
- раздел README лаб «Общие правила ступени 1» переименован в «Как принято
на ступени 1» и переписан как выбор с доводом; точечно поправлены
«Самопроверка» и таблица ступени 1.
- Проверка:
- grep по «ё», «менти», формам «ты» и словам-законам по docs/labs/*.md
молчит (единственное «правило» — в нетронутом разделе «Уборка»).
- скрипт ссылок и якорей из #226 печатает пусто; git diff --check чист.
- сверка имен в обратных кавычках и чекбоксов старых и новых файлов —
без расхождений.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
dc2cbc99dc
commit
29a01f0016
6 files changed
+472
-522
No files matched your search
@@ -1,121 +1,118 @@
|
||||
# Задание 3: `dm.session_overview`
|
||||
|
||||
**Чему учит:** агрегат хранит слагаемые, а не готовые средние и доли.
|
||||
Среднее по строкам витрины не складывается никогда, а уникальных нельзя
|
||||
складывать даже тогда, когда на сегодняшних данных сумма сходится.
|
||||
**Чему учит:** хранить слагаемые, а не готовые средние и доли. Понимать,
|
||||
почему сумма `visitors` по строкам сейчас сходится, но начнет завышать
|
||||
аудиторию, если у одной куки появятся визиты с двух устройств.
|
||||
|
||||
## Сначала прочитать
|
||||
|
||||
- [«Хранилище и его слои»](../handbook/storage-layers.md);
|
||||
- [«Потоки Airflow и даги модельного времени»](../handbook/airflow-dags.md).
|
||||
|
||||
## Роль витрины
|
||||
|
||||
Дневной портрет визитов в разрезе устройств: сколько было сессий, какие они
|
||||
были длинные, сколько страниц смотрели и как часто доходили до покупки.
|
||||
Вопрос витрины — «как люди ходят по магазину с телефона и с настольного
|
||||
компьютера, и чем эти визиты отличаются».
|
||||
Витрина нужна, чтобы по дням сравнивать визиты с телефона и компьютера: их
|
||||
число, длительность, просмотры страниц и частоту покупок.
|
||||
|
||||
Сессия на стенде — расчетная сущность, ее уже нарезал слой DDS. Собирать
|
||||
визиты заново из событий не нужно и нельзя: это была бы вторая нарезка,
|
||||
которая тихо разойдется с первой.
|
||||
На прошлом стенде витрина выделяла визиты сама. Теперь их рассчитывают в
|
||||
DDS, а повторять зерно сессий ради представления с переименованными
|
||||
колонками незачем. Здесь вы учитесь дневной агрегации, поэтому зерно — день
|
||||
и устройство. Выбор описан в разделе «Форма: по зерну, не по цене запроса»
|
||||
[справочника DM](../architecture/dm.md).
|
||||
|
||||
Витрина с таким именем была и на прошлом стенде, но зерно у нее сменилось,
|
||||
и сменилось не по вкусу. Там визитов как сущности не было, и витрина
|
||||
нарезала их сама, строкой на визит. Здесь визит — сущность слоя DDS, и
|
||||
витрина, повторяющая его зерно, оказалась бы обычным представлением по
|
||||
[правилу формы](../architecture/dm.md): копией сессий с другими именами
|
||||
колонок. Чтобы задание осталось про дневной агрегат, витрина поднялась на
|
||||
зерно выше — до дня и среза.
|
||||
## Что сделать
|
||||
|
||||
## Зерно и форма
|
||||
Источник — `dds.session_v`. Зерно — **день сессии × категория устройства**.
|
||||
Зерно новое, поэтому витрина хранится готовым агрегатом — см. [«Как принято
|
||||
на ступени 1»](README.md#как-принято-на-ступени-1). Создайте пять объектов:
|
||||
`dm.session_overview_rep` / `_dist`, донор `_stage_rep` / `_stage_dist` и
|
||||
публичное представление `dm.session_overview_v`. Ключ партиции — день.
|
||||
|
||||
Зерно — **день сессии × категория устройства**. Источник —
|
||||
`dds.session_v`, у него зерно визита, значит зерно сменилось и витрина
|
||||
материализуется: `dm.session_overview_rep` / `_dist`, донор `_stage_rep` /
|
||||
`_stage_dist`, публичное `dm.session_overview_v`. Ключ партиции — день.
|
||||
### Состав колонок
|
||||
|
||||
Категория устройства взята из сессии как есть: у куки она постоянна, и слой
|
||||
DDS уже перевел код источника в слово. Словарь расшифровки шире, чем мир
|
||||
стенда: слов в нем четыре, а встречаются три — `desktop`, `mobile` и
|
||||
`tablet`. Строку под `tv` заводить незачем, она возникнет сама, если такие
|
||||
визиты появятся.
|
||||
|
||||
## Состав колонок
|
||||
|
||||
| Колонка | Тип | Что это |
|
||||
| Колонка | Тип | Значение |
|
||||
|---|---|---|
|
||||
| `report_date` | `Date` | день сессии |
|
||||
| `device_category` | `LowCardinality(String)` | категория устройства |
|
||||
| `sessions` | `UInt64` | число визитов |
|
||||
| `visitors` | `UInt64` | число разных `client_id` |
|
||||
| `bounce_sessions` | `UInt64` | визиты ровно из одного события |
|
||||
| `purchase_sessions` | `UInt64` | визиты, в которых была хотя бы одна покупка |
|
||||
| `duration_seconds_total` | `UInt64` | суммарная длительность визитов |
|
||||
| `events_total` | `UInt64` | событий во всех визитах |
|
||||
| `pageviews` | `UInt64` | просмотров страниц |
|
||||
| `cart_adds` | `UInt64` | добавлений в корзину |
|
||||
| `purchases` | `UInt64` | покупок |
|
||||
| `_load_id` | `String` | запуск, собравший день |
|
||||
| `_load_ts` | `DateTime64(3, 'UTC')` | когда собран |
|
||||
| `report_date` | `Date` | День сессии |
|
||||
| `device_category` | `LowCardinality(String)` | Категория устройства |
|
||||
| `sessions` | `UInt64` | Число визитов |
|
||||
| `visitors` | `UInt64` | Число разных `client_id` |
|
||||
| `bounce_sessions` | `UInt64` | Визиты ровно из одного события |
|
||||
| `purchase_sessions` | `UInt64` | Визиты хотя бы с одной покупкой |
|
||||
| `duration_seconds_total` | `UInt64` | Суммарная длительность визитов |
|
||||
| `events_total` | `UInt64` | Число событий во всех визитах |
|
||||
| `pageviews` | `UInt64` | Число просмотров страниц |
|
||||
| `cart_adds` | `UInt64` | Число добавлений в корзину |
|
||||
| `purchases` | `UInt64` | Число покупок |
|
||||
| `_load_id` | `String` | Идентификатор запуска, собравшего день |
|
||||
| `_load_ts` | `DateTime64(3, 'UTC')` | Время сборки |
|
||||
|
||||
Средней длительности и доли отказов в витрине нет намеренно. Хранить
|
||||
`avg_duration_seconds` было бы удобнее ровно один раз — пока смотрят одну
|
||||
строку. Тот, кто захочет среднее по дню целиком, сложит средние по трем
|
||||
устройствам и получит неправду: средние не складываются, они взвешиваются.
|
||||
Пара «`duration_seconds_total` и `sessions`» дает верный ответ на любом
|
||||
срезе, а деление стоит потребителю один вызов. То же и с долей отказов:
|
||||
храним `bounce_sessions` и `sessions`, долю считает тот, кто спрашивает.
|
||||
Возьмите `device_category` из сессии: DDS уже заменил код словом. Из четырех
|
||||
категорий встречаются `desktop`, `mobile` и `tablet`. Строку для `tv`
|
||||
создавайте только при появлении таких визитов.
|
||||
|
||||
С `visitors` история тоньше, и в ней главная ловушка задания. Складывать
|
||||
уникальных по строкам нельзя: один и тот же посетитель, попавший в две
|
||||
строки, будет посчитан дважды. Проверьте на стенде — и увидите, что сумма
|
||||
`visitors` по устройствам за день **сходится** с `dm.daily_traffic_v`.
|
||||
Вместо `avg_duration_seconds` храните слагаемые. Среднее из средних по трем
|
||||
устройствам даст неверный ответ за день: число сессий в категориях
|
||||
различается, поэтому средние нужно взвешивать. Потребитель делит сумму
|
||||
`duration_seconds_total` на сумму `sessions`. Долю отказов считают так же:
|
||||
сумму `bounce_sessions` делят на сумму `sessions`, а не усредняют готовые
|
||||
доли.
|
||||
|
||||
Это не отменяет правила, а показывает, откуда берется совпадение: в мире
|
||||
стенда устройство приписано куке на всю жизнь ([словарь
|
||||
понятий](../../CONTEXT.md), «Паспорт куки»), поэтому одна кука лежит ровно
|
||||
в одной строке дня, и пересекаться строкам не в чем.
|
||||
### Ритм пересборки
|
||||
|
||||
Совпадение это — свойство мира, а не свойство витрины. Появятся визиты
|
||||
одной куки с двух устройств — сумма поедет, и ни одна проверка об этом не
|
||||
скажет: отчет просто начнет тихо завышать аудиторию. Отсюда правило,
|
||||
которое стоит унести с задания: складывать уникальных нельзя даже там, где
|
||||
сегодня получается. Число посетителей дня спрашивают у
|
||||
`dm.daily_traffic_v`, где оно посчитано на своем зерне.
|
||||
Сессии прожитого дня больше не меняются. Как в задании 2, определяйте
|
||||
диапазон от первого несобранного прожитого дня до последнего прожитого.
|
||||
Соберите его в донор и замените дневные партиции. Добавьте задачу в
|
||||
`dm_transform`.
|
||||
|
||||
## Ритм пересборки
|
||||
### Ключ шардирования
|
||||
|
||||
Источник — сессии: день замерзает, как только прожит. Объем прогона тот же,
|
||||
что в задании 2, — хвост от первого несобранного прожитого дня; механика та
|
||||
же: донор и замена дневных партиций.
|
||||
Выберите ключ самостоятельно. Визитов с планшета мало, с телефонов и
|
||||
компьютеров — значительно больше. Если хотите взять хеш только от
|
||||
`device_category`, сначала посчитайте строки на каждом шарде. Затем
|
||||
объясните, почему такое распределение вас устраивает.
|
||||
|
||||
## Ключ шардирования
|
||||
Ключ донора — тот же, что у цели (см. [«Как принято на ступени
|
||||
1»](README.md#как-принято-на-ступени-1) и раздел «Раскладка по шардам»
|
||||
[справочника хранилища](../architecture/storage.md)).
|
||||
|
||||
Ваше решение по [правилу донора](../architecture/storage.md), «Раскладка по
|
||||
шардам». Здесь у измерения всего три встречающихся значения, и распределены
|
||||
они неровно: планшеты — тонкая полоса, а телефоны и настольные машины держат
|
||||
основную массу. Прежде чем брать хеш от одного `device_category`, посчитайте,
|
||||
сколько строк ляжет на каждый шард, и назовите, чем вас устраивает результат.
|
||||
## Почему сумма `visitors` сходится с `dm.daily_traffic_v`
|
||||
|
||||
Сравните сумму `visitors` по устройствам за день с `dm.daily_traffic_v`. На
|
||||
стенде значения совпадут: устройство приписано куке на всю жизнь, поэтому за
|
||||
день она учитывается только в одной строке. Это свойство мира стенда; см.
|
||||
раздел «Паспорт куки» в [словаре понятий](../../CONTEXT.md).
|
||||
|
||||
Если появятся визиты одной куки с двух устройств, сумма начнет завышать
|
||||
аудиторию. Проверки счетчиков по категориям этого не обнаружат. Число
|
||||
посетителей дня берите из `dm.daily_traffic_v`, где оно рассчитано для дня
|
||||
целиком.
|
||||
|
||||
## Типичная ошибка
|
||||
|
||||
Повторное выделение визитов из событий может дать другие границы сессий, чем
|
||||
в DDS. Используйте готовые сессии из `dds.session_v`. Если общее число
|
||||
событий совпало с источником, а число сессий завышено, вероятная причина —
|
||||
сборка витрины из событий вместо сессий.
|
||||
|
||||
## Как проверить себя
|
||||
|
||||
Деловой ключ — `device_category` внутри дня. Соберите прямой агрегат из
|
||||
`dds.session_v` за день, соедините с `dm.session_overview_v` полным внешним
|
||||
соединением и посчитайте строки, где ключа нет с одной из сторон или где
|
||||
расходится любой счетчик. Ноль — зеленый.
|
||||
Деловой ключ — `device_category` внутри дня. Получите прямой агрегат из
|
||||
`dds.session_v` за день. Соедините его с `dm.session_overview_v` полным
|
||||
внешним соединением по ключу. Посчитайте строки, где ключ отсутствует с
|
||||
одной из сторон или различается любой счетчик. Расхождений должно быть ноль.
|
||||
|
||||
Две проверки покрупнее ловят грубую ошибку сразу:
|
||||
Еще две проверки дневных итогов:
|
||||
|
||||
- сумма `sessions` за день равна числу строк `dds.session_v` этого дня:
|
||||
ни один визит не потерян и ни один не задвоен;
|
||||
- сумма `events_total` за день равна сумме `events_total` по сессиям того
|
||||
же дня.
|
||||
|
||||
Если суммы событий сходятся, а сумма сессий больше числа визитов — почти
|
||||
наверняка витрина собрана из событий, а не из сессий.
|
||||
- сумма `sessions` равна числу строк `dds.session_v` за день — без потерь и
|
||||
повторного учета визитов;
|
||||
- сумма `events_total` равна сумме `events_total` по сессиям того же дня.
|
||||
|
||||
## Приемка
|
||||
|
||||
- [ ] пять объектов витрины созданы, донор объявлен через `AS` и шардируется
|
||||
ключом цели;
|
||||
- [ ] `dm.session_overview_v` перечисляет колонки явно;
|
||||
- [ ] источник витрины — `dds.session_v`, своей нарезки визитов в ней нет;
|
||||
- [ ] средних и долей в физической таблице нет;
|
||||
- [ ] задача `session_overview` в `homework_check` зеленая;
|
||||
- [ ] созданы пять объектов, донор объявлен через `AS` и шардируется ключом
|
||||
цели;
|
||||
- [ ] в `dm.session_overview_v` колонки перечислены явно;
|
||||
- [ ] источник — `dds.session_v`, повторного выделения визитов нет;
|
||||
- [ ] физическая таблица не хранит средние и доли;
|
||||
- [ ] задача `session_overview` в `homework_check` завершилась успешно;
|
||||
- [ ] задача витрины добавлена в `dm_transform`.
|
||||
Reference in new issue
Block a user