Зачем: дать практику учета новых связей пользователей при обновлении прежних дней витрины. Что: добавлены две связанные лабораторные с наблюдениями через Airflow, собственными таблицами student и повтором частично записанной загрузки. Обновлены программа, индекс и ссылки из справочников DDS и DM. Проверка: DDL и SQL под student на ClickHouse 26.3.17.56, четыре модельных дня через Airflow, полные сверки и повтор незавершенной загрузки; config-test, lint, локальные ссылки и независимое ревью.
123 lines
9.1 KiB
Markdown
123 lines
9.1 KiB
Markdown
# Карта идентичностей в 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` между разными ключами
|
||
шардирования.
|