- Зачем: - согласованная модель нужна для пакетной реализации DDS и понятного публичного интерфейса слоя. - Что: - описаны физическая карта идентичностей, полный пересчёт и отказ от first_matched_at. - унифицирован публичный контракт _v для событий, заказов, сессий и карты. - синхронизированы мастер-спека, имена объектов и граница доступа. - Проверка: - git diff --cached --check.
9.1 KiB
Карта идентичностей в 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 нет. Запланированные витрины его не читают, а два возможных
смысла — «первая покупка вообще» и «момент, с которого связь была известна
хранилищу» — требуют разных исторических моделей. Этот вопрос оставлен
самостоятельной работе,
а текущий стенд не выдаёт один смысл за другой.
Статус заказа связь не меняет: любой заказ, сопоставленный с событием покупки, показывает владельца куки. Отмена меняет состояние заказа, но не личность покупателя.
Физическая модель и публичный интерфейс
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. Роли объектов и доступ следуют общей
конвенции, «Имена объектов».
Шардирование совпадает с событиями и сессиями. Благодаря этому витрина может
обогащать их картой локально по client_id; точную форму распределённого
запроса решает проектирование витрин #135.
Сборка
Карта собирается один раз в суточной пакетной цепочке после обновления модели заказов и до зависимых витрин. При догоняющем запуске за несколько дней хватает одной сборки после всей пачки заказов.
Источники — вся доступная история dds.event_v и текущее состояние заказов
dds.order_v. Трансформация:
- оставляет события
purchaseи разворачивает единственныйpurchase_id; - соединяет его с
order_idзаказа черезGLOBAL INNER JOIN; - выбирает различные пары
client_id,user_id; - добавляет координаты текущего запуска и пишет полный набор в
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 — связи сущностей и публичная поверхность
_v. - Модель заказа — текущее состояние заказа и пакетное наполнение.
- Контракт события — поля событий на языке DDS.
- Исследование распределённых соединений
— форма
GLOBAL JOINи капканINSERT SELECTмежду разными ключами шардирования.