feat(dds): добавлена карта идентичностей

- Зачем:
  - реализованы требования issue #153 к мосту между кукой и пользователем.
- Что:
  - добавлены физическая карта, точное представление и полный пересчёт через GLOBAL JOIN.
  - задача identity_map поставлена после сборки заказов в dds_transform.
  - устройство и живые проверки карты записаны в документации хранилища.
- Проверка:
  - make lint, make smoke, make check-clickhouse, make check-services.
  - три запуска dds_transform: 4770 физических строк и 1590 уникальных пар в _v.
This commit is contained in:
ddadmin committed 2026-08-26 12:30:40 -04:00
1 parent cdd44c469f
commit 39ff0b5bb4
5 files changed
+124 -21

No files matched your search

+33 -7
View File
@@ -1,11 +1,7 @@
-- DDS: физические модели слоя и доноры их партиций.
-- DDS: физические модели слоя и доноры партиций.
--
-- Идемпотентность здесь устроена иначе, чем в приёме, и это главный урок обеих
-- сущностей файла. В ODS повтор гасит колонка версии: ReplacingMergeTree по
-- snapshot_date оставляет строку позднейшего слепка. Здесь колонки
-- версии нет вовсе — повтор гасит замена партиции целиком: день собирается
-- заново и подменяется одним атомарным движением. Поэтому строка таблицы
-- ровно одна на своё зерно, а чтению не нужен FINAL.
-- Заказ и сессия гасят повтор заменой дневной партиции, без колонки версии.
-- Карта идентичностей пересчитывается целиком и пишет новую версию пары.
-- Локальная таблица заказа.
--
@@ -170,3 +166,33 @@ ORDER BY (client_id, started_at);
CREATE TABLE IF NOT EXISTS dds.session_stage_dist ON CLUSTER clickstream_cluster
AS dds.session_stage_rep
ENGINE = Distributed('clickstream_cluster', 'dds', 'session_stage_rep', cityHash64(client_id));
-- DDS: известная связь куки трекера с пользователем магазина.
--
-- Полный запуск снова пишет все известные пары. Физические повторы до
-- фонового слияния допустимы: _load_ts выбирает последнюю версию пары.
-- Партиции нет, потому что карта пересчитывается целиком и её зерно не
-- связано с днём.
CREATE TABLE IF NOT EXISTS dds.identity_map_rep ON CLUSTER clickstream_cluster
(
client_id UInt64,
user_id UInt64,
_load_id String,
_load_ts DateTime64(3, 'UTC')
)
ENGINE = ReplicatedReplacingMergeTree(
'/clickhouse/tables/{shard}/{database}/{table}',
'{replica}',
_load_ts
)
ORDER BY (client_id, user_id);
-- Карта лежит по ключу будущего соединения с событиями и сессиями.
CREATE TABLE IF NOT EXISTS dds.identity_map_dist ON CLUSTER clickstream_cluster
AS dds.identity_map_rep
ENGINE = Distributed(
'clickstream_cluster',
'dds',
'identity_map_rep',
cityHash64(client_id)
);
+14
View File
@@ -134,3 +134,17 @@ SELECT
_load_id,
_load_ts
FROM dds.session_dist;
-- DDS: точная известная связь куки с пользователем магазина.
--
-- Полный пересчёт оставляет физические версии одной пары до слияния частей.
-- FINAL прячет это устройство от читателя; явный список колонок не даёт
-- служебным изменениям таблицы случайно расширить договор.
CREATE VIEW IF NOT EXISTS dds.identity_map_v ON CLUSTER clickstream_cluster
AS
SELECT
client_id,
user_id,
_load_id,
_load_ts
FROM dds.identity_map_dist FINAL;
+46
View File
@@ -0,0 +1,46 @@
-- DDS: полный пересчёт известных пар куки с пользователем.
--
-- События разложены по client_id; заказы по order_id. Ключи соединения не
-- ко-локированы, поэтому правило слоя требует явный GLOBAL. С подзапросами с
-- обеих сторон ClickHouse 26.3 выполняет соединение один раз на инициаторе
-- (docs/research/2026-08-22-distributed-join.md).
INSERT INTO dds.identity_map_dist
(
client_id,
user_id,
_load_id,
_load_ts
)
SELECT
client_id,
user_id,
{load_id:String},
now64(3, 'UTC')
FROM
(
SELECT DISTINCT
purchase.client_id,
orders.user_id
FROM
(
SELECT
client_id,
-- По договору события purchase_id содержит ровно один номер
-- заказа: одно подтверждение оформляет один заказ (спека, раздел 4).
purchase_id[1] AS order_id
FROM dds.event_v
WHERE event_type = 'purchase'
) AS purchase
GLOBAL INNER JOIN
(
SELECT
order_id,
user_id
FROM dds.order_v
) AS orders ON purchase.order_id = orders.order_id
)
-- В этой форме ClickHouse 26.3 уже пишет через инициатор. Явный ноль фиксирует
-- раскладку по ключу цели, если форма запроса или план изменятся (капкан #137).
SETTINGS distributed_foreground_insert = 1,
parallel_distributed_insert_select = 0;