docs(dds): зафиксирована физическая модель карты идентичностей
- Зачем: - согласованная модель нужна для пакетной реализации DDS и понятного публичного интерфейса слоя. - Что: - описаны физическая карта идентичностей, полный пересчёт и отказ от first_matched_at. - унифицирован публичный контракт _v для событий, заказов, сессий и карты. - синхронизированы мастер-спека, имена объектов и граница доступа. - Проверка: - git diff --cached --check.
This commit is contained in:
1 parent
256d51840b
commit
f0c4b4c627
7 files changed
+234
-59
No files matched your search
@@ -0,0 +1,122 @@
|
||||
# Карта идентичностей в 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` нет. Запланированные витрины его не читают, а два возможных
|
||||
смысла — «первая покупка вообще» и «момент, с которого связь была известна
|
||||
хранилищу» — требуют разных исторических моделей. Этот вопрос оставлен
|
||||
[самостоятельной работе](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/145),
|
||||
а текущий стенд не выдаёт один смысл за другой.
|
||||
|
||||
Статус заказа связь не меняет: любой заказ, сопоставленный с событием покупки,
|
||||
показывает владельца куки. Отмена меняет состояние заказа, но не личность
|
||||
покупателя.
|
||||
|
||||
## Физическая модель и публичный интерфейс
|
||||
|
||||
- `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` между разными ключами
|
||||
шардирования.
|
||||
Reference in new issue
Block a user