Зачем: дать практику учета новых связей пользователей при обновлении прежних дней витрины. Что: добавлены две связанные лабораторные с наблюдениями через Airflow, собственными таблицами student и повтором частично записанной загрузки. Обновлены программа, индекс и ссылки из справочников DDS и DM. Проверка: DDL и SQL под student на ClickHouse 26.3.17.56, четыре модельных дня через Airflow, полные сверки и повтор незавершенной загрузки; config-test, lint, локальные ссылки и независимое ревью.
147 lines
10 KiB
Markdown
147 lines
10 KiB
Markdown
# Как обновлять витрину без полной пересборки
|
||
|
||
**Чему учит:** выбирать дни для пересчета по новым данным, проверять результат
|
||
и повторять незавершенную загрузку.
|
||
|
||
Факультатив повышенной сложности продолжает работу
|
||
[«Когда хранилище узнало пользователя»](identity-over-time.md).
|
||
Понадобятся ее запрос сохранения связей и таблица `default.lab_identity_history`.
|
||
Работайте под `student` через то же подключение DBeaver.
|
||
|
||
Стенд пересчитывает `dm.daily_traffic_v` целиком: новая связь может изменить
|
||
число известных пользователей в старом дне. Вы построите рядом свою витрину,
|
||
которая пересчитывает только затронутые дни и дает тот же результат.
|
||
|
||
В опыте появляются новые дни сессий и новые связи. Мир стенда не отзывает
|
||
связи и не исправляет старые сессии при обычном продвижении времени.
|
||
Удаление данных, исправления старых сессий и пересоздание мира потребовали бы
|
||
другого способа обнаружения изменений.
|
||
|
||
## Подготовьте витрину и журнал загрузок
|
||
|
||
Создайте две локальные таблицы. Если имена заняты, выберите свои.
|
||
|
||
```sql
|
||
CREATE TABLE default.lab_daily_traffic
|
||
(
|
||
report_date Date,
|
||
visitors UInt64,
|
||
known_users UInt64,
|
||
observation_day Date
|
||
)
|
||
ENGINE = ReplacingMergeTree(observation_day)
|
||
ORDER BY report_date;
|
||
|
||
CREATE TABLE default.lab_traffic_runs
|
||
(
|
||
observation_day Date
|
||
)
|
||
ENGINE = MergeTree
|
||
ORDER BY observation_day;
|
||
```
|
||
|
||
В витрине одна актуальная строка на день. Для обновления вставляйте полный
|
||
новый результат дня с новой `observation_day`; читайте его с `FINAL`.
|
||
Движок выбирает максимальную `observation_day` для каждого `report_date`.
|
||
Старая версия может исчезнуть при слиянии: эта таблица хранит текущий расчет,
|
||
а не историю наблюдений.
|
||
|
||
Журнал хранит даты успешно завершенных загрузок. Добавляйте дату только
|
||
после записи всех выбранных дней и успешной сверки. Последняя строка журнала
|
||
будет границей, от которой вы ищете новые связи в следующем прогоне.
|
||
|
||
## Выполните первую загрузку целиком
|
||
|
||
Убедитесь, что `world_live` стоит на паузе и нет активных прогонов генерации
|
||
и обработки. Запустите `etl_pipeline` и дождитесь успеха. Получите текущую
|
||
`observation_day` способом из первой работы. Если такого наблюдения еще нет
|
||
в вашей истории, сохраните его; уже сохраненную дату повторно не вставляйте.
|
||
|
||
Напишите `INSERT INTO ... SELECT`, который рассчитывает все даты сессий из
|
||
`dds.session_v` со связями текущего наблюдения. Правила результата:
|
||
|
||
| Колонка | Что считать |
|
||
|---|---|
|
||
| `report_date` | `session_date` |
|
||
| `visitors` | число разных `client_id` |
|
||
| `known_users` | число разных известных `user_id` |
|
||
| `observation_day` | дата текущего наблюдения |
|
||
|
||
Сессии без известного пользователя сохраняйте. Несколько кук одного
|
||
пользователя не должны увеличивать `known_users`. Для соединения с локальной
|
||
историей связей используйте `GLOBAL LEFT JOIN`.
|
||
|
||
Построчно сверьте свои `report_date`, `visitors`, `known_users` с
|
||
`dm.daily_traffic_v`. Сверка должна находить лишние и отсутствующие даты
|
||
и различия обоих показателей. Учебную витрину читайте с `FINAL`.
|
||
После совпадения запишите дату наблюдения в журнал один раз.
|
||
|
||
## Выберите дни следующего пересчета
|
||
|
||
Продвиньте мир на один день: `world_next_day` с `days = 1`, затем
|
||
`etl_pipeline`; после каждого запуска дождитесь успеха. Сохраните новое
|
||
наблюдение связей запросом из первой работы.
|
||
|
||
Обозначим дату последней успешной загрузки из журнала **P**, текущую дату
|
||
наблюдения — **H**. До записи результата составьте запрос, возвращающий
|
||
разные даты для пересчета. Он должен учесть два изменения:
|
||
|
||
- в сессиях появились дни, которых еще нет в вашей витрине;
|
||
- в карте появились пары, которых не было при P; у этих кук есть сессии,
|
||
в том числе за прежние дни.
|
||
|
||
Сначала выведите даты и разберите одну старую дату: какая новая пара привела
|
||
к ее выбору и какие сессии с ней связаны? Рядом посчитайте, сколько дат
|
||
пересчитала бы полная загрузка. Зафиксируйте оба числа; равенство тоже
|
||
возможно, если новые связи затронули всю историю.
|
||
|
||
Проверьте альтернативу: искать изменения по `first_purchase_at`, взяв покупки
|
||
только после конца P. Сравните полученный набор пар с разностью наблюдений.
|
||
Покажите пару, которую такое ограничение пропускает.
|
||
|
||
## Обновите выбранные дни
|
||
|
||
Напишите второй `INSERT INTO ... SELECT`. Для выбранных дат он должен
|
||
рассчитать все сессии с полной картой связей на H. Прибавление числа новых
|
||
связей к прежнему `known_users` не заменяет пересчет разных пользователей.
|
||
|
||
Вставьте результаты с версией H. Полностью сверьте актуальную учебную витрину
|
||
со стендовой, включая дни, которые не пересчитывали. Только после совпадения
|
||
добавьте H в журнал.
|
||
|
||
Без продвижения мира повторите выбор дат. После успешной загрузки новых дат
|
||
и пар быть не должно, а результат витрины должен остаться прежним.
|
||
Повторно записывать H в журнал не требуется.
|
||
|
||
## Повторите незавершенную загрузку
|
||
|
||
Снова запустите `world_next_day` с `days = 1`, затем `etl_pipeline`,
|
||
дожидаясь успеха каждого. Сохраните новое наблюдение связей.
|
||
Получите даты для пересчета, но запишите результат только за одну из
|
||
них. Остальные пока не загружайте и дату в журнал не добавляйте.
|
||
Так вы воспроизведете остановку между записями дней, не ломая стенд.
|
||
|
||
Запустите свой обычный расчет еще раз, определив P по журналу. Он должен
|
||
довести загрузку до совпадения со стендовой витриной без очистки уже
|
||
записанных строк. После сверки запишите H в журнал.
|
||
|
||
Объясните на этом опыте, почему последнее сохраненное наблюдение нельзя
|
||
автоматически считать загруженным в витрину. Что изменится, если записать H
|
||
в журнал до обновления витрины?
|
||
|
||
## Что должно получиться
|
||
|
||
Сохраните SQL первой загрузки, выбора дат, обновления и сверки.
|
||
После двух новых модельных дней ваша витрина совпадает со стендовой,
|
||
повтор без новых данных сохраняет результат, а незавершенную загрузку
|
||
можно продолжить без полной пересборки.
|
||
|
||
Сравните цену двух вариантов: сколько дней вы пересчитывали и какие
|
||
дополнительные данные хранили. По этому опыту можно судить об объеме работы,
|
||
но не об ускорении: на небольшом стенде полный пересчет может быть дешевле.
|
||
В вашей реализации поиск связей пока читает всю историю покупок —
|
||
инкрементальным стал именно пересчет дневной витрины.
|
||
|
||
После работы удалите свои `lab_daily_traffic`, `lab_traffic_runs` и
|
||
`lab_identity_history` через `DROP TABLE`, если они больше не нужны.
|