Files
ddadmin b839a671a1 docs(labs): добавлены история связей и инкрементальный пересчет
Зачем: дать практику учета новых связей пользователей при обновлении
прежних дней витрины.

Что: добавлены две связанные лабораторные с наблюдениями через Airflow,
собственными таблицами student и повтором частично записанной загрузки.
Обновлены программа, индекс и ссылки из справочников DDS и DM.

Проверка: DDL и SQL под student на ClickHouse 26.3.17.56, четыре модельных
дня через Airflow, полные сверки и повтор незавершенной загрузки;
config-test, lint, локальные ссылки и независимое ревью.
2026-09-22 05:57:11 -04:00

123 lines
9.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Карта идентичностей в DDS
Учебный результат: менти строит физический мост между кукой трекера и
пользователем магазина, раскладывает его по ключу будущего соединения и видит,
как пакетный полный пересчёт сочетается с точным публичным представлением.
## Назначение
Карта — адресная книга: «эта кука уже покупала как этот пользователь
магазина». Связь становится известна только после покупки, потому что в
кликстриме есть `client_id`, а `user_id` живёт в заказе. Две куки одного
покупателя дают две строки с одним `user_id`; поэтому витрина может показать
рядом число посетителей по кукам и число известных людей после склейки.
Это не полная клиентская база. Куки без покупки остаются анонимными, а карта не
пытается угадать их владельца.
## Зерно и состав строки
Одна строка — известная пара `client_id` × `user_id`. Деловых колонок две:
- `client_id UInt64` — кука трекера;
- `user_id UInt64` — пользователь магазина.
Собственные служебные колонки `_load_id String` и
`_load_ts DateTime64(3, 'UTC')` показывают, каким запуском и когда пара записана
в DDS. Они имеют читателя при расследовании повтора и потому входят в публичный
договор вместе с деловой парой.
`first_matched_at` нет. Запланированные витрины его не читают, а два возможных
смысла — «первая покупка вообще» и «момент, с которого связь была известна
хранилищу» — требуют разных исторических моделей. Этот вопрос оставлен
[самостоятельной работе](../../labs/optional/identity-over-time.md),
а текущий стенд не выдаёт один смысл за другой.
Статус заказа связь не меняет: любой заказ, сопоставленный с событием покупки,
показывает владельца куки. Отмена меняет состояние заказа, но не личность
покупателя.
## Физическая модель и публичный интерфейс
- `dds.identity_map_rep` — локальная
`ReplicatedReplacingMergeTree(_load_ts)`, `ORDER BY (client_id, user_id)`,
без партиционирования;
- `dds.identity_map_dist` — распределённая поверхность записи, шардирование
по `cityHash64(client_id)`;
- `dds.identity_map_v` — публичное обычное представление над `_dist`, которое
перечисляет четыре колонки явно и применяет `FINAL`.
Повторы полного запуска физически могут лежать рядом до фонового слияния.
`FINAL` оставляет одну последнюю версию пары, поэтому пользователь всегда читает
точную карту через `_v`. Роли объектов и доступ следуют общей
[конвенции](../storage.md), «Имена объектов».
Шардирование совпадает с событиями и сессиями. Благодаря этому витрина может
обогащать их картой локально по `client_id`; точную форму распределённого
запроса решает проектирование витрин #135.
## Сборка
Карта собирается один раз в суточной пакетной цепочке после обновления модели
заказов и до зависимых витрин. При догоняющем запуске за несколько дней хватает
одной сборки после всей пачки заказов.
Источники — вся доступная история `dds.event_v` и текущее состояние заказов
`dds.order_v`. Трансформация:
1. оставляет события `purchase` и разворачивает единственный `purchase_id`;
2. соединяет его с `order_id` заказа через `GLOBAL INNER JOIN`;
3. выбирает различные пары `client_id`, `user_id`;
4. добавляет координаты текущего запуска и пишет полный набор в
`dds.identity_map_dist`.
`GLOBAL` нужен потому, что события разложены по `client_id`, а заказы — по
`order_id`: строки соединения не ко-локированы. Правая сторона мала, поэтому
рассылка заказов приемлема. Материализованное представление здесь не подходит:
заказ может появиться в пакетном слепке позже уже принятого события покупки.
## Повтор запуска
Каждый запуск заново получает все известные пары из источников. Он не читает
предыдущую карту и не делает anti-join «вставить только отсутствующее».
`_load_ts` служит версией `ReplacingMergeTree`, а `dds.identity_map_v` прячет
физические повторы через `FINAL`.
Полный пересчёт выбран потому, что карта стенда мала, источники уже содержат
нужную историю, а повтор задачи не зависит от того, на каком месте оборвался
предыдущий. Мир стенда не отзывает связь куки с пользователем, поэтому удалять
исчезнувшие пары не требуется.
## Читатели
- `dm.daily_traffic_v` — посетители по `client_id` и известные пользователи по
`user_id` рядом;
- учебные запросы — показать, как несколько кук одного человека меняют
`uniq`;
- расследование пакетного запуска — `_load_id` и `_load_ts` текущей версии
пары.
## Отклонённые варианты
- **`first_matched_at` в текущей модели.** Нет запланированного читателя и нет
однозначной временной семантики; идея передана в issue #145.
- **Словарь ClickHouse.** Для малой карты мог бы ускорить точечное обогащение,
но спрятал бы физическую модель и не добавил нового урока рядом с уже
существующим словарём товаров.
- **Одно динамическое представление без физической карты.** Повторяло бы
соединение всей истории при каждом чтении, не давало ко-локации результата и
не показывало устройство реального слоя хранения.
- **Инкремент через anti-join с самой картой.** Делал бы результат предыдущего
запуска обязательным входом и усложнял повтор без пользы на объёме стенда.
- **Материализованное представление от событий.** Не увидело бы заказ, который
приехал позже события покупки.
## Связанные решения
- [Карта DDS](README.md) — связи сущностей и публичная поверхность `_v`.
- [Модель заказа](order.md) — текущее состояние заказа и пакетное наполнение.
- [Контракт события](event.md) — поля событий на языке DDS.
- [Исследование распределённых соединений](../../research/2026-08-22-distributed-join.md)
— форма `GLOBAL JOIN` и капкан `INSERT SELECT` между разными ключами
шардирования.