diff --git a/docs/labs/README.md b/docs/labs/README.md index 877ff75..7b82156 100644 --- a/docs/labs/README.md +++ b/docs/labs/README.md @@ -142,7 +142,7 @@ Airflow; для нее вы сохраняете состояния заказо Открывается после ступени 1. -- [Когда хранилище узнало пользователя](optional/identity-over-time.md): +- [Как новые заказы меняют отчет о посетителях](optional/identity-over-time.md): сохранить историю связей, сравнить расчеты на прежних сессиях и определить, какие сведения нужны для воспроизведения результата. diff --git a/docs/labs/optional/identity-over-time.md b/docs/labs/optional/identity-over-time.md index e10ace0..7140157 100644 --- a/docs/labs/optional/identity-over-time.md +++ b/docs/labs/optional/identity-over-time.md @@ -1,19 +1,21 @@ -# Когда хранилище узнало пользователя +# Как новые заказы меняют отчет о посетителях -**Чему учит:** различать время события и время появления связи в хранилище, -выбирать сведения для обогащения данных на дату расчета. +**Чему учит:** сохранять историю связей посетителей с покупателями +и использовать ее для воспроизведения прежнего расчета. Факультатив доступен после ступени 1. Понадобятся Airflow и DBeaver с подключением `student` по [инструкции](../../../README.md#подключиться-из-dbeaver). -Наблюдения сохраняйте в `default` через одно подключение: ваши таблицы -локальны узлу. Если предложенное имя занято, выберите свое. +Результаты запросов сохраняйте в `default` через одно подключение: +ваши таблицы будут доступны только на узле этого подключения. +Если предложенное имя таблицы занято, выберите свое. -На сайте посетителя узнают по куке `client_id`, а магазин знает покупателя -по `user_id`. Связь между ними можно установить, когда событие покупки -сопоставлено с заказом. Она позволяет обогатить и прежние визиты этой куки. -Вы проверите, как новый заказ меняет результат расчета за уже прошедший день. +В событиях сайта посетитель обозначен кукой `client_id`, а в заказах +покупатель — `user_id`. Сопоставив событие покупки с заказом, можно связать +эти идентификаторы. Тогда и к прежним визитам этой куки можно добавить +пользователя магазина. Вы проверите, как появление такой связи меняет число +известных пользователей в отчете за прошедший день. -## Соберите связи с подтверждением +## Сопоставьте посетителей с покупателями Поставьте `world_live` на паузу, дождитесь завершения начатых прогонов генерации и обработки. Запустите `etl_pipeline` и дождитесь успеха. @@ -26,8 +28,9 @@ | `dds.event_v` | `event_type`, `event_time`, `client_id`, `purchase_id` | | `dds.order_v` | `order_id`, `user_id` | -В результате нужна одна строка на пару `client_id`, `user_id` и самое раннее -время найденной покупки, подтверждающей эту пару. Назовите его `first_purchase_at`. +В результате нужна одна строка на пару `client_id`, `user_id`. Для каждой пары +найдите самое раннее время события покупки, которое удалось сопоставить +с заказом. Назовите эту колонку `first_purchase_at`. В событии `purchase` массив `purchase_id` содержит один номер заказа. Статус заказа не ограничивайте: отмена покупки не меняет владельца куки. @@ -37,9 +40,10 @@ Проверьте уникальность пар и совпадение их полного набора с `dds.identity_map_v`: одного равенства количества недостаточно. -Для одной пары покажите номера заказов и события, по которым нашли время. +Для одной пары покажите номера заказов и события, по которым получили +`first_purchase_at`. -## Сохраните три наблюдения +## Сохраните список связей после каждого модельного дня Создайте таблицу: @@ -55,16 +59,19 @@ ENGINE = MergeTree ORDER BY (observation_day, client_id, user_id); ``` -Для метки наблюдения используйте последний обработанный день событий: +Каждая копия списка должна содержать все найденные пары. В колонку +`observation_day` запишите последний обработанный день событий: ```sql SELECT max(event_date) AS observation_day FROM dds.event_v; ``` -Обозначим начальную дату **H0**. Это модельная дата, а не календарный день -запуска Airflow. После успешного `etl_pipeline` сохраните полный результат -своего запроса через `INSERT INTO ... SELECT`, добавив эту дату. -Проверьте число строк и уникальность пар за H0. +Обозначим полученную дату **H0**. Она показывает, после обработки какого +модельного дня сохранен список; календарная дата запуска Airflow здесь +не используется. Сохраните полный результат своего запроса через +`INSERT INTO ... SELECT`, добавив H0 в `observation_day` каждой строки. +Сверьте число записанных строк с результатом исходного запроса и проверьте +уникальность пар для этой даты. Затем дважды выполните последовательность: @@ -72,62 +79,63 @@ SELECT max(event_date) AS observation_day FROM dds.event_v; 2. Запустите `etl_pipeline` и дождитесь успеха. 3. Получите новую `observation_day`, сохраните полный набор связей и проверьте его. -Получатся наблюдения **H0**, **H1** и **H2**. Каждую дату записывайте один раз: -обычный `MergeTree` не устраняет повторы вставки. Между этими действиями -не запускайте генерацию отдельно от наблюдения. +Получатся три копии списка: после дней **H0**, **H1** и **H2**. +Для каждой даты сохраняйте список один раз: обычный `MergeTree` +не устраняет повторы вставки. Сохраните и проверьте список перед тем, +как запускать следующий модельный день. -## Сопоставьте два времени +## Сравните дату покупки с датой появления связи -Сравните наборы пар в соседних наблюдениях. Для нескольких новых связей -покажите `client_id`, `user_id`, `first_purchase_at` и первую дату наблюдения, -в которой пара появилась. Найдите связь, покупка которой произошла до дня -ее обнаружения. +Сравните списки за соседние даты: H0 с H1, затем H1 с H2. Найдите пары, +которых не было в прежнем списке. Для нескольких таких пар покажите +`client_id`, `user_id`, `first_purchase_at` и первую `observation_day`, +в которой пара появилась. Найдите пример, где покупка произошла раньше +этой даты. Для одной такой связи вернитесь к заказу и событию. По данным и [порядку доставки заказов](../../architecture/orders/ingestion.md) объясните, -почему связь отсутствовала в прежнем наблюдении. Был ли момент покупки -достаточным основанием считать ее уже известной хранилищу? +почему пары не было в прежнем списке. Какие данные понадобились для ее +появления и когда они стали доступны? -У пары, которая присутствовала уже в H0, тоже получится минимальная дата -наблюдения. В этом случае она означает «пара была известна к началу опыта»: -истории до H0 у вас нет. Для новых пар ежедневные наблюдения устанавливают -интервал появления связи, а не точное время внутри него. +Для пары, которая присутствовала уже в H0, нельзя определить дату появления: +у вас нет более ранних копий списка. Для новой пары можно установить +интервал между двумя сохранениями, но не точное время внутри него. -## Пересчитайте прежние визиты +## Пересчитайте отчет за прежние дни Возьмите один и тот же набор сессий из `dds.session_v` за даты до H0 включительно. Постройте два дневных расчета: -- со связями, сохраненными в H0; -- со связями, сохраненными в H2. +- со списком связей на H0; +- со списком связей на H2. Для каждого дня посчитайте посетителей по разным `client_id` и известных пользователей по разным `user_id`. Сессии без связи должны остаться в расчете, но неизвестный пользователь не должен увеличивать число известных. -В соединении распределенных сессий с локальной историей используйте -`GLOBAL LEFT JOIN` и заранее отберите в истории одну дату наблюдения. +В соединении сессий с вашей таблицей используйте `GLOBAL LEFT JOIN`. +В таблице связей заранее отберите одну `observation_day`, чтобы копии +списка за разные дни не смешались. Найдите день с разным числом известных пользователей. Проследите изменение до конкретных связей и прежних сессий. Проверьте, что число посетителей -не изменилось и известные пользователи не превышают его. +не изменилось, а число известных пользователей не больше числа посетителей. -Напишите, какой вариант нужен для двух требований к витрине: - -- «пересчитать прошлые визиты с учетом всех сведений, доступных сейчас»; -- «воспроизвести расчет, сделанный при наблюдении H0». - -Для второго требования проверьте еще один способ: взять текущие связи, -оставив только `first_purchase_at` до конца H0. Совпадет ли полный набор -пар с сохраненным в H0? Найденным расхождением обоснуйте выбор истории -для воспроизводимого расчета. +Теперь проверьте, можно ли воспроизвести расчет на H0 без сохраненного +списка за эту дату. Возьмите список на H2 и оставьте только пары, +у которых `first_purchase_at` приходится на H0 или более ранний день. +Сравните полный набор пар с копией на H0. Выведите пары, которые есть +только в одном из списков. +На примере найденных различий объясните, какие данные нужно сохранять, +чтобы повторить прежний расчет. ## Что должно получиться -У вас есть запрос связей с подтверждающими покупками, три сохраненных -наблюдения и сравнение двух расчетов на одном наборе сессий. Вы можете -показать, когда новая связь стала видна в наблюдениях, и объяснить, -какие данные понадобятся для воспроизведения прежнего расчета. +У вас есть запрос сопоставления посетителей с покупателями, три сохраненных +списка связей и сравнение двух расчетов на одном наборе сессий. На конкретных +парах вы можете показать разницу между датой покупки и датой появления пары +в сохраненном списке, а также объяснить, что нужно для повторения прежнего +расчета. -Сохраните SQL и таблицу наблюдений: они нужны в продолжении +Сохраните SQL и таблицу `lab_identity_history`: они нужны в продолжении [«Как обновлять витрину без полной пересборки»](incremental-daily-traffic.md). Если продолжать не планируете, удалите свою таблицу через `DROP TABLE`. diff --git a/docs/labs/optional/incremental-daily-traffic.md b/docs/labs/optional/incremental-daily-traffic.md index d09d34c..195ee06 100644 --- a/docs/labs/optional/incremental-daily-traffic.md +++ b/docs/labs/optional/incremental-daily-traffic.md @@ -4,20 +4,21 @@ и повторять незавершенную загрузку. Факультатив повышенной сложности продолжает работу -[«Когда хранилище узнало пользователя»](identity-over-time.md). -Понадобятся ее запрос сохранения связей и таблица `default.lab_identity_history`. +[«Как новые заказы меняют отчет о посетителях»](identity-over-time.md). +Понадобятся ее запрос сохранения списка связей и таблица `default.lab_identity_history`. Работайте под `student` через то же подключение DBeaver. Стенд пересчитывает `dm.daily_traffic_v` целиком: новая связь может изменить число известных пользователей в старом дне. Вы построите рядом свою витрину, которая пересчитывает только затронутые дни и дает тот же результат. -В опыте появляются новые дни сессий и новые связи. Мир стенда не отзывает -связи и не исправляет старые сессии при обычном продвижении времени. +При обычном продвижении модельного времени появляются новые дни сессий +и новые связи. Уже найденные пары сохраняются, а сессии прежних дней +не меняются. Удаление данных, исправления старых сессий и пересоздание мира потребовали бы другого способа обнаружения изменений. -## Подготовьте витрину и журнал загрузок +## Создайте витрину и журнал успешных загрузок Создайте две локальные таблицы. Если имена заняты, выберите свои. @@ -41,93 +42,96 @@ ORDER BY observation_day; ``` В витрине одна актуальная строка на день. Для обновления вставляйте полный -новый результат дня с новой `observation_day`; читайте его с `FINAL`. -Движок выбирает максимальную `observation_day` для каждого `report_date`. -Старая версия может исчезнуть при слиянии: эта таблица хранит текущий расчет, -а не историю наблюдений. +новый результат дня с новой `observation_day`. Читайте витрину с `FINAL`: +для каждого `report_date` останется строка с максимальной `observation_day`. +Старые версии могут исчезнуть при слиянии, +поэтому для воспроизведения прежнего расчета нужна сохраненная история связей. -Журнал хранит даты успешно завершенных загрузок. Добавляйте дату только -после записи всех выбранных дней и успешной сверки. Последняя строка журнала -будет границей, от которой вы ищете новые связи в следующем прогоне. +В журнал записывайте `observation_day` списка связей, использованного +для расчета. Добавляйте дату только после записи всех выбранных дней +и успешной сверки. Максимальная дата в журнале укажет, какой список +использовался в последней завершенной загрузке. ## Выполните первую загрузку целиком Убедитесь, что `world_live` стоит на паузе и нет активных прогонов генерации и обработки. Запустите `etl_pipeline` и дождитесь успеха. Получите текущую -`observation_day` способом из первой работы. Если такого наблюдения еще нет -в вашей истории, сохраните его; уже сохраненную дату повторно не вставляйте. +`observation_day` способом из первой работы. Если списка за эту дату еще нет +в вашей истории, сохраните его. Повторно вставлять уже сохраненный список +не требуется. Напишите `INSERT INTO ... SELECT`, который рассчитывает все даты сессий из -`dds.session_v` со связями текущего наблюдения. Правила результата: +`dds.session_v` со списком связей на текущую дату. Правила результата: | Колонка | Что считать | |---|---| | `report_date` | `session_date` | | `visitors` | число разных `client_id` | | `known_users` | число разных известных `user_id` | -| `observation_day` | дата текущего наблюдения | +| `observation_day` | дата выбранной копии списка связей | -Сессии без известного пользователя сохраняйте. Несколько кук одного -пользователя не должны увеличивать `known_users`. Для соединения с локальной -историей связей используйте `GLOBAL LEFT JOIN`. +Сессии без известного пользователя сохраняйте в расчете. Несколько кук одного +пользователя не должны увеличивать `known_users`. Для соединения с вашей +таблицей связей используйте `GLOBAL LEFT JOIN`, отобрав одну `observation_day`. Построчно сверьте свои `report_date`, `visitors`, `known_users` с `dm.daily_traffic_v`. Сверка должна находить лишние и отсутствующие даты и различия обоих показателей. Учебную витрину читайте с `FINAL`. После совпадения запишите дату наблюдения в журнал один раз. -## Выберите дни следующего пересчета +## Найдите дни, которые нужно пересчитать Продвиньте мир на один день: `world_next_day` с `days = 1`, затем -`etl_pipeline`; после каждого запуска дождитесь успеха. Сохраните новое -наблюдение связей запросом из первой работы. +`etl_pipeline`; после каждого запуска дождитесь успеха. Сохраните новый +список связей запросом из первой работы. -Обозначим дату последней успешной загрузки из журнала **P**, текущую дату -наблюдения — **H**. До записи результата составьте запрос, возвращающий -разные даты для пересчета. Он должен учесть два изменения: +Обозначим максимальную `observation_day` в журнале **P**, а дату нового +списка связей — **H**. До записи результата составьте запрос, возвращающий +даты для пересчета без повторов. Он должен учесть два изменения: - в сессиях появились дни, которых еще нет в вашей витрине; -- в карте появились пары, которых не было при P; у этих кук есть сессии, - в том числе за прежние дни. +- в списке на H появились пары, которых не было в списке на P; у этих кук + есть сессии, в том числе за прежние дни. -Сначала выведите даты и разберите одну старую дату: какая новая пара привела -к ее выбору и какие сессии с ней связаны? Рядом посчитайте, сколько дат -пересчитала бы полная загрузка. Зафиксируйте оба числа; равенство тоже -возможно, если новые связи затронули всю историю. +Сначала выведите даты и выберите один прежний день: какая новая пара +потребовала его пересчета и какие сессии с ней связаны? Рядом посчитайте, +сколько дат пересчитала бы полная загрузка. Зафиксируйте оба числа; +равенство тоже возможно, если новые связи затронули всю историю. -Проверьте альтернативу: искать изменения по `first_purchase_at`, взяв покупки -только после конца P. Сравните полученный набор пар с разностью наблюдений. -Покажите пару, которую такое ограничение пропускает. +Для сравнения выберите из списка на H пары, у которых день покупки +по `first_purchase_at` позже P. Совпадет ли результат с новыми парами, +найденными сравнением списков? Покажите пару, которую ограничение по дате +покупки пропускает. ## Обновите выбранные дни Напишите второй `INSERT INTO ... SELECT`. Для выбранных дат он должен -рассчитать все сессии с полной картой связей на H. Прибавление числа новых +рассчитать все сессии с полным списком связей на H. Прибавление числа новых связей к прежнему `known_users` не заменяет пересчет разных пользователей. Вставьте результаты с версией H. Полностью сверьте актуальную учебную витрину со стендовой, включая дни, которые не пересчитывали. Только после совпадения добавьте H в журнал. -Без продвижения мира повторите выбор дат. После успешной загрузки новых дат -и пар быть не должно, а результат витрины должен остаться прежним. +Без продвижения мира снова определите P по журналу и повторите выбор дат. +Запрос должен вернуть пустой результат, а витрина — остаться прежней. Повторно записывать H в журнал не требуется. -## Повторите незавершенную загрузку +## Проверьте повторный запуск после частичной загрузки Снова запустите `world_next_day` с `days = 1`, затем `etl_pipeline`, -дожидаясь успеха каждого. Сохраните новое наблюдение связей. +дожидаясь успеха каждого. Сохраните новый список связей с его датой H. Получите даты для пересчета, но запишите результат только за одну из них. Остальные пока не загружайте и дату в журнал не добавляйте. Так вы воспроизведете остановку между записями дней, не ломая стенд. -Запустите свой обычный расчет еще раз, определив P по журналу. Он должен -довести загрузку до совпадения со стендовой витриной без очистки уже -записанных строк. После сверки запишите H в журнал. +Снова определите P по журналу, выполните запрос выбора дат и пересчет +этих дней. Проверьте, что результат совпал со стендовой витриной без очистки +уже записанных строк. После сверки запишите H в журнал. -Объясните на этом опыте, почему последнее сохраненное наблюдение нельзя -автоматически считать загруженным в витрину. Что изменится, если записать H -в журнал до обновления витрины? +Объясните на этом опыте, почему сохранение нового списка связей еще +не означает, что витрина обновлена. Что изменится, если записать H в журнал +до обновления витрины? ## Что должно получиться @@ -136,9 +140,9 @@ ORDER BY observation_day; повтор без новых данных сохраняет результат, а незавершенную загрузку можно продолжить без полной пересборки. -Сравните цену двух вариантов: сколько дней вы пересчитывали и какие -дополнительные данные хранили. По этому опыту можно судить об объеме работы, -но не об ускорении: на небольшом стенде полный пересчет может быть дешевле. +Сравните число пересчитанных дней с полной загрузкой и перечислите, +какие дополнительные данные пришлось хранить. Так можно оценить объем работы. +На небольшом стенде полный пересчет может быть дешевле. В вашей реализации поиск связей пока читает всю историю покупок — инкрементальным стал именно пересчет дневной витрины. diff --git a/docs/specs/2026-09-01-labs-program.md b/docs/specs/2026-09-01-labs-program.md index 406ef08..ed6e519 100644 --- a/docs/specs/2026-09-01-labs-program.md +++ b/docs/specs/2026-09-01-labs-program.md @@ -207,7 +207,7 @@ Открывается после ступени 1 основной программы. -- **[Когда хранилище узнало пользователя](../labs/optional/identity-over-time.md) (#145).** +- **[Как новые заказы меняют отчет о посетителях](../labs/optional/identity-over-time.md) (#145).** Учащийся собирает связи с подтверждающими покупками и сохраняет три ежедневных наблюдения. На одном наборе прежних сессий сравнивает расчет с прежними и новыми связями, выбирает историю для воспроизведения расчета.