Merge pull request 'feat(dds): добавлена карта идентичностей' (#168) from 153-dds-identity-map into main
Reviewed-on: #168
This commit was merged in pull request #168.
This commit is contained in:
commit
c9bcacd63b
5 files changed
+124
-21
No files matched your search
+19
-9
@@ -1,18 +1,18 @@
|
|||||||
"""Трансформации слоя DDS: сборка модели заменой дневных партиций.
|
"""Трансформации слоя DDS: сборка моделей.
|
||||||
|
|
||||||
Даг собирает модель поверх принятого ODS. Сущностей у него две — заказ и
|
Заказ и сессия собираются заменой дневных партиций. Карта идентичностей
|
||||||
сессия, зависимости по данным между ними нет; карта идентичностей встанет
|
пересчитывается целиком после заказа, потому что читает его текущую модель.
|
||||||
соседней группой задач.
|
|
||||||
|
|
||||||
Расписания у дага нет и параметров тоже: объём каждого прогона он выводит сам
|
Расписания у дага нет и параметров тоже: объём каждого прогона он выводит сам
|
||||||
из состояния слоёв и позиции мира, поэтому один и тот же запуск одинаково
|
из состояния слоёв и позиции мира, поэтому один и тот же запуск одинаково
|
||||||
верен из триггера дневного конвейера, с кнопки менти и после падения
|
верен из триггера дневного конвейера, с кнопки менти и после падения
|
||||||
(docs/architecture/etl.md, «Объём прогона»).
|
(docs/architecture/etl.md, «Объём прогона»).
|
||||||
|
|
||||||
Повтор прогона гасит не колонка версии, а замена партиции целиком: день
|
Заказ и сессия гасят повтор заменой партиции целиком: день собирается в
|
||||||
собирается в таблицу-двойник и подменяет собой партицию цели. Почему слой
|
таблицу-двойник и подменяет собой партицию цели. Карта пишет полный набор пар
|
||||||
устроен так — sql/ddl/45-dds-tables.sql; механика самой подмены —
|
новой версией, а точное чтение даёт FINAL. Замена партиции разобрана в
|
||||||
sql/dds/order_replace.sql.
|
sql/dds/order_replace.sql; точное чтение карты описано в
|
||||||
|
sql/ddl/50-dds-views.sql.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -229,8 +229,18 @@ def dds_transform():
|
|||||||
|
|
||||||
rebuild >> replace
|
rebuild >> replace
|
||||||
|
|
||||||
order()
|
orders = order()
|
||||||
session()
|
session()
|
||||||
|
|
||||||
|
identity_map = SQLExecuteQueryOperator(
|
||||||
|
task_id="identity_map",
|
||||||
|
conn_id=CLICKHOUSE_CONNECTION,
|
||||||
|
sql="dds/identity_map_rebuild.sql",
|
||||||
|
parameters={"load_id": "{{ run_id }}"},
|
||||||
|
do_xcom_push=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
orders >> identity_map
|
||||||
|
|
||||||
|
|
||||||
dds_transform()
|
dds_transform()
|
||||||
@@ -587,6 +587,8 @@ SQL-файлы исполняет штатный `SQLExecuteQueryOperator`. Пр
|
|||||||
| `dds` | `dds.session_rep` / `_dist` | визит, нарезанный слоем; партиция — день сессии |
|
| `dds` | `dds.session_rep` / `_dist` | визит, нарезанный слоем; партиция — день сессии |
|
||||||
| `dds` | `dds.session_stage_rep` / `_dist` | двойник сессии: в нём день собирается перед заменой партиции |
|
| `dds` | `dds.session_stage_rep` / `_dist` | двойник сессии: в нём день собирается перед заменой партиции |
|
||||||
| `dds` | `dds.session_v` | публичный договор сессии |
|
| `dds` | `dds.session_v` | публичный договор сессии |
|
||||||
|
| `dds` | `dds.identity_map_rep` / `_dist` | известная пара куки с пользователем; таблицы полного пересчёта |
|
||||||
|
| `dds` | `dds.identity_map_v` | точный публичный договор карты идентичностей с `FINAL` |
|
||||||
| `dic` | `dic.products_file` | чтец CSV-каталога, общего с генератором |
|
| `dic` | `dic.products_file` | чтец CSV-каталога, общего с генератором |
|
||||||
| `dic` | `dic.products` | словарь товаров поверх подложки |
|
| `dic` | `dic.products` | словарь товаров поверх подложки |
|
||||||
|
|
||||||
@@ -594,11 +596,11 @@ SQL-файлы исполняет штатный `SQLExecuteQueryOperator`. Пр
|
|||||||
`INSERT SELECT` одной задачи `parse_batch` из файла
|
`INSERT SELECT` одной задачи `parse_batch` из файла
|
||||||
`sql/ods/order_parse.sql`.
|
`sql/ods/order_parse.sql`.
|
||||||
|
|
||||||
В слое DDS построены событие, заказ и сессия, и устроены они намеренно
|
В слое DDS построены событие, заказ, сессия и карта идентичностей. Событие
|
||||||
по-разному: событие остаётся представлением над ODS, у заказа и сессии —
|
остаётся представлением над ODS; заказ и сессия гасят повтор заменой партиции;
|
||||||
физические модели. Карту идентичностей и слой DM принесут следующие тикеты
|
карта пишет полный набор пар в версионную таблицу. Слой DM принесут следующие
|
||||||
этапа. Их состав задан разделом 7 мастер-спеки и переносится сюда по мере
|
тикеты этапа. Его состав задан разделом 7 мастер-спеки и переносится сюда по
|
||||||
постройки.
|
мере постройки.
|
||||||
|
|
||||||
## Что проверено
|
## Что проверено
|
||||||
|
|
||||||
@@ -818,6 +820,11 @@ Kafka с пустым значением (ноль байт) и запись-н
|
|||||||
двойник разложила 1662 заказа строго по ключу: шард 1 получил только чётные
|
двойник разложила 1662 заказа строго по ключу: шард 1 получил только чётные
|
||||||
`cityHash64(order_id)`, шард 2 — только нечётные.
|
`cityHash64(order_id)`, шард 2 — только нечётные.
|
||||||
|
|
||||||
|
**Проверка карты идентичностей 26 августа 2026 года (#153).** Context7
|
||||||
|
подтвердил версию в `ReplacingMergeTree` и точное чтение через `FINAL`. Два
|
||||||
|
полных прогона оставили 3180 физических строк и 1590 пар в `_v`; все пары
|
||||||
|
подтверждены покупкой и заказом, а строк на чужом шарде нет.
|
||||||
|
|
||||||
Тем же днём измерены ещё три поведения ClickHouse.
|
Тем же днём измерены ещё три поведения ClickHouse.
|
||||||
|
|
||||||
- **Параметр в `ALTER … PARTITION` не подставляется.** Запрос с `{day:Date}` в
|
- **Параметр в `ALTER … PARTITION` не подставляется.** Запрос с `{day:Date}` в
|
||||||
|
|||||||
@@ -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
|
CREATE TABLE IF NOT EXISTS dds.session_stage_dist ON CLUSTER clickstream_cluster
|
||||||
AS dds.session_stage_rep
|
AS dds.session_stage_rep
|
||||||
ENGINE = Distributed('clickstream_cluster', 'dds', 'session_stage_rep', cityHash64(client_id));
|
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)
|
||||||
|
);
|
||||||
@@ -134,3 +134,17 @@ SELECT
|
|||||||
_load_id,
|
_load_id,
|
||||||
_load_ts
|
_load_ts
|
||||||
FROM dds.session_dist;
|
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;
|
||||||
@@ -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;
|
||||||
Reference in new issue
Block a user