diff --git a/AGENTS.md b/AGENTS.md index 0b73c53..e6b700f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -164,7 +164,9 @@ Airflow) и названия из кода. Если для понятия ес - Имена файлов в `docs/handbook/` — слаг строчными латинскими буквами через дефис, индекс папки — `README.md`. Здесь живет путеводитель по стенду: учебные главы для менти. Ни дат, ни номеров: глава — живой документ и - правится вместе с тем, что описывает. + правится вместе с тем, что описывает. Исходники интерактивной карты лежат + подпапкой `architecture-map/`, а ее рендер — файлом `architecture-map.html` + рядом с главами. - Имена файлов в `docs/adr/` — `NNNN-краткое-имя.md`: сквозной номер из четырёх цифр и слаг (`0001-stand-services.md`). Решения нумеруются подряд, дата в имени не нужна. diff --git a/Makefile b/Makefile index 6fe145f..eb50d65 100644 --- a/Makefile +++ b/Makefile @@ -51,9 +51,11 @@ config-test: # того своя дверь и свой конфиг. Версия закреплена, потому что лока в корне # нет, а форматтер между версиями меняет вывод — иначе проверка однажды # покраснела бы сама, без единой правки в репозитории. +# Проверка рендера быстрая, не требует стенда и ловит устаревший HTML. lint: - uvx ruff@0.16.1 check dags infra/superset - uvx ruff@0.16.1 format --check dags infra/superset + uvx ruff@0.16.1 check dags infra/superset docs/handbook/architecture-map + uvx ruff@0.16.1 format --check dags infra/superset docs/handbook/architecture-map + uv run docs/handbook/architecture-map/render.py --check # --- Наполнение миром --- diff --git a/docs/architecture/testing.md b/docs/architecture/testing.md index 3364601..7b0f457 100644 --- a/docs/architecture/testing.md +++ b/docs/architecture/testing.md @@ -36,13 +36,13 @@ Стенд нужен трём целям из восьми. Цена — замер, см. «Что проверено». `make smoke` и `make check-services` перемерены 28 августа 2026 года, `make config-test` — 21 августа, `make check-clickhouse` — 19 августа, -корневой `make lint` — 9 августа, `make test` — 30 августа; остальные стоят с +корневой `make lint` — 3 сентября, `make test` — 30 августа; остальные стоят с замера 6 августа. | Цель | Откуда | Что утверждает | Стенд | Цена | |---|---|---|---|---| | `make config-test` | корень | Compose разбирается, файлы DAG и дашборды Grafana синтаксически целы, в diff нет пробельных ошибок. О работоспособности не говорит ничего | не нужен | 1 с | -| `make lint` | корень | Код стенда — `dags/` и `infra/superset/` — отформатирован и проходит ruff | не нужен | 0,4 с | +| `make lint` | корень | Код стенда и сборщик карты отформатированы и проходят ruff; готовый HTML карты соответствует исходникам | не нужен | 0,8 с | | `make smoke` | корень | Стенд **собран**: службы живы, порты отвечают, подключения настроены друг на друга. Вширь и по касательной к каждой службе. Единственная цель, которая здесь правда смоук | нужен | 25 с | | `make check-clickhouse` | корень | Всё, что спрашивают **у ClickHouse** и он отвечает сам: макросы, шарды, реплики, путь в keeper, ключ шардирования, очередь распределённых DDL, ответ словаря товаров, счёт событий стартового мира против паспорта | нужен | 24 с | | `make check-services` | корень | **Службы работают**: DAG запускается и доходит, топик создаётся и удаляется, Superset логинится и ходит в базу | нужен | 71 с | @@ -128,7 +128,7 @@ ClickHouse отвечает сразу. Правки генератора добавляют к этому `make lint`, `make typecheck` и `make test` из каталога `generator/`: стенд им не нужен, а `make test` из них самая -дорогая. Правки дагов и Superset — корневой `make lint`. +дорогая. Правки дагов, Superset и карты — корневой `make lint`. Двадцать секунд из ее ста семидесяти шести — пересборка восьми модельных дней для сверки с паспортом мира (20,5 с из 176,5). Дешевле хеши не сравнить: чтобы @@ -286,6 +286,10 @@ Kafka по той же причине спрашивают снаружи. Её ## Что проверено +**Перезамер 3 сентября 2026 года при исполнении #222.** `make lint` прошел за +0,768 секунды; в таблице 0,8 с. `ruff format --check` проверил 13 файлов стенда, +включая исходники карты, а затем проверка рендера подтвердила свежесть HTML. + **Калибровка заказной стороны 30 августа 2026 года при исполнении #194.** Пересборки эталонного мира не будет (#192), поэтому доли заказной стороны сверены замером внутренностей генератора — окно 28 модельных дней, @@ -474,11 +478,11 @@ shell-версия узкой пересборки за 2 м 25 с пересо 24 секунды. Это цена всей цели, а не одной новой строки. **Замер 9 августа 2026 года при появлении корневого `lint` (#65).** Три прогона -подряд — 0,39, 0,36 и 0,39 секунды на трёх файлах стенда; в таблице 0,4 с. На +подряд — 0,39, 0,36 и 0,39 секунды на трех файлах стенда, которые тогда входили в проверку; в таблице 0,4 с. На машине с непрогретым кешем первый прогон дороже: `uvx` сначала скачивает ruff. Тогда же снято, что двери не перекрываются: `ruff check --show-files` из корня -перечисляет ровно три файла стенда, из `generator/` — только файлы генератора. +перечислял ровно три файла стенда, из `generator/` — только файлы генератора. Корневой конфиг правила генератору не подменяет: конфиг ищется вверх по дереву от файла, и ближний выигрывает. diff --git a/docs/handbook/README.md b/docs/handbook/README.md index ab95d89..76a16a6 100644 --- a/docs/handbook/README.md +++ b/docs/handbook/README.md @@ -14,6 +14,13 @@ Первая статья словаря объясняет хранилище. Следом идет несколько статей об устройстве генератора; они понадобятся нескоро. +## Карта хранилища + +[Интерактивная карта](architecture-map.html) показывает путь данных через слои. +Gitea показывает HTML как текст. Откройте файл из своей копии репозитория +или скачайте его кнопкой «Raw» и откройте в браузере. +Держите карту рядом с главой «Хранилище и его слои». + ## Главы ### [Хранилище и его слои](storage-layers.md) diff --git a/docs/handbook/architecture-map.html b/docs/handbook/architecture-map.html new file mode 100644 index 0000000..c493bc9 --- /dev/null +++ b/docs/handbook/architecture-map.html @@ -0,0 +1,1501 @@ + + + + + + + Карта слоев хранилища + + + +
+

+

+
+
+
+
+
+
+
+ +
+ +
+ + + diff --git a/docs/handbook/architecture-map/README.md b/docs/handbook/architecture-map/README.md new file mode 100644 index 0000000..a94a896 --- /dev/null +++ b/docs/handbook/architecture-map/README.md @@ -0,0 +1,9 @@ +# Интерактивная карта хранилища + +`map.yaml` хранит слои, узлы, стрелки и тексты карточек. +`template.html` задает внешний вид и поведение карты. +`render.py` вкладывает данные в шаблон и пишет файл рядом с главами. +Правьте исходники, а не `architecture-map.html`. +Пересоберите карту командой: +`uv run docs/handbook/architecture-map/render.py`. +Проверьте свежесть готового файла с флагом `--check`. diff --git a/docs/handbook/architecture-map/map.yaml b/docs/handbook/architecture-map/map.yaml new file mode 100644 index 0000000..aa1ad18 --- /dev/null +++ b/docs/handbook/architecture-map/map.yaml @@ -0,0 +1,678 @@ +title: Карта слоев хранилища +lead: Проследите, как один смысл меняет имя и поверхность между слоями. +chapter_url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/handbook/storage-layers.md#user-content-хранилище-и-его-слои +intro: + - Эта карта показывает путь событий и заказов от Kafka до витрин DM. + - Щелкните по узлу, чтобы увидеть его назначение, устройство и ключи. + - Щелкните по стрелке, чтобы узнать, кто и каким запросом переносит данные. + - Когда вы выбираете узел, карта подсвечивает все пути к нему и от него. + - Переключатель потока приглушает другую часть карты, но не меняет раскладку. + - Сначала прочитайте главу «Хранилище и его слои», затем вернитесь к карте и рассмотрите объекты. + +streams: + all: Все + events: События + orders: Заказы + +dictionary: + title: Словарь + caption: 'сбоку цепочки: справочники' + +layers: + - id: kafka + title: Kafka + caption: откуда приходит + - id: stg + title: STG + caption: как поступило + - id: ods + title: ODS + caption: язык источника + - id: dds + title: DDS + caption: язык бизнеса + - id: dm + title: DM + caption: данные для дашбордов + +mechanisms: + - id: materialized_view + label: матвью + line: solid + rhythm: непрерывно, по мере прихода сообщений + - id: airflow_dag + label: даг Airflow + line: dashed + rhythm: 'пачкой: раз в модельные сутки (`orders_ingest`) или на каждом прогоне конвейера' + - id: view + label: представление + line: dotted + rhythm: без копии, данные читаются на месте + +nodes: + - id: kafka_hits + name: hits + layer: kafka + order: 0 + streams: [events] + what: Топик Kafka с событиями кликстрима. Каждое сообщение содержит одно событие. + read: 'Топик напрямую не читают: таблица `stg.hits_raw_kafka` забирает сообщения из топика. Для данных событий используйте `stg.hits_raw_dist`.' + fill: + - Источник событий пишет сообщения в топик в темпе выбранного режима мира. + links: + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/storage.md#user-content-при%D1%91м-поток-и-его-свойства + - label: Формат + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/formats/clickstream-event.md#user-content-описание-выгрузки-событие-кликстрима + + - id: kafka_orders + name: orders + layer: kafka + order: 1 + streams: [orders] + what: Топик Kafka со слепками заказов магазина. После модельного дня приходит снимок изменяемого окна. + read: 'Топик напрямую не читают: таблица `stg.orders_raw_kafka` забирает сообщения из топика. Для заказов используйте `stg.orders_raw_dist`.' + fill: + - Источник заказов публикует слепок пачкой после каждого модельного дня. + links: + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/orders/ingestion.md#user-content-поток-данных + - label: Устройство слепка + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/orders/snapshot.md#user-content-слепок-и-его-доставка + + - id: stg_hits + name: stg.hits_raw_dist + layer: stg + order: 0 + streams: [events] + what: Сырая строка события и метаданные доставки. STG сохраняет сообщение без разбора. + read: 'Читайте `_dist`: у STG нет представления `_v`, а распределенная таблица показывает оба шарда.' + parts: + - '`stg.hits_raw_kafka` — чтец топика `hits`.' + - '`stg.hits_raw_rep` — реплицируемая таблица на шарде.' + - '`stg.hits_raw_dist` — распределенная поверхность записи и чтения.' + - '`stg.hits_raw_mv` — матвью приема сообщений из чтеца.' + fill: + - '`stg.hits_raw_mv` пишет непрерывно, по мере прихода сообщений.' + - Сырые строки живут трое суток реального времени плюс хвост до суток. + links: + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/storage.md#user-content-при%D1%91м-поток-и-его-свойства + - label: DDL таблиц + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/10-stg-tables.sql + - label: DDL матвью + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/40-stg-views.sql + + - id: stg_orders + name: stg.orders_raw_dist + layer: stg + order: 1 + streams: [orders] + what: Сырые строки слепка заказов, метаданные доставки и идентификатор пачки `_load_id`. + read: 'Читайте `_dist`: у STG нет представления `_v`, а распределенная таблица показывает оба шарда.' + parts: + - '`stg.orders_raw_kafka` — чтец топика `orders` на первой ноде.' + - '`stg.orders_raw_rep` — реплицируемая таблица на шарде.' + - '`stg.orders_raw_dist` — распределенная поверхность записи и чтения.' + fill: + - Задача `pull_batch` дага `orders_ingest` забирает слепок пачкой раз в модельные сутки. + - Сырые строки живут трое суток реального времени плюс хвост до суток. + links: + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/orders/ingestion.md#user-content-поток-данных + - label: DDL + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/10-stg-tables.sql + + - id: ods_event + name: ods.event_v + layer: ods + order: 0 + streams: [events] + what: Типизированное широкое событие с именами полей источника. Запрос представления с `FINAL` возвращает актуальную версию события. + read: 'Читайте `ods.event_v`: запрос представления с `FINAL` возвращает актуальные строки вместо физических версий.' + parts: + - '`ods.event_rep` — реплицируемая таблица версий события на шарде.' + - '`ods.event_dist` — распределенная поверхность записи и диагностики.' + - '`ods.event_v` — представление актуального состояния с `FINAL`.' + - '`ods.event_mv` — матвью разбора годных строк STG.' + keys: + - '`WatchID` — тот же идентификатор, что `event_id` в DDS.' + - '`ClientID` — тот же посетитель, что `client_id` в DDS.' + - '`EventDate` — тот же день, что `event_date` в DDS.' + fill: + - '`ods.event_mv` разбирает строки непрерывно, по мере записи в STG.' + - '`ReplacingMergeTree(_load_ts)` хранит версии, а `FINAL` выбирает актуальную.' + links: + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/storage.md#user-content-актуальные-события + - label: DDL таблиц + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/20-ods-tables.sql + - label: DDL представлений + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/30-ods-views.sql + + - id: ods_event_errors + name: ods.event_errors_dist + layer: ods + order: 1 + streams: [events] + what: Сырые события, которые не прошли строгий прием, с классом ошибки и метаданными доставки. + read: 'Читайте `_dist`: представления `_v` нет, а распределенная таблица показывает ошибки с обоих шардов.' + parts: + - '`ods.event_errors_rep` — реплицируемая таблица ошибок на шарде.' + - '`ods.event_errors_dist` — распределенная поверхность чтения и записи.' + - '`ods.event_errors_mv` — матвью, которое отбирает негодные строки STG.' + fill: + - '`ods.event_errors_mv` пишет непрерывно, по мере записи в STG.' + - Ошибки живут один месяц и не останавливают прием годных событий. + links: + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/storage.md#user-content-таблица-ошибок + - label: DDL таблиц + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/20-ods-tables.sql + - label: DDL матвью + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/30-ods-views.sql + + - id: ods_order + name: ods.order_v + layer: ods + order: 2 + streams: [orders] + what: Текущая версия заказа на языке источника. Позиции пока остаются сырым фрагментом JSON. + read: 'Читайте `ods.order_v`: запрос представления с `FINAL` возвращает последнюю версию по `snapshot_date`.' + parts: + - '`ods.order_rep` — реплицируемая таблица версий заказа на шарде.' + - '`ods.order_dist` — распределенная поверхность записи и диагностики.' + - '`ods.order_v` — представление текущего состояния с `FINAL`.' + keys: + - '`order_id` — тот же заказ в `dds.order_v` и `dm.purchase_vs_orders_v`.' + - '`user_id` — пользователь магазина, который переходит в заказ DDS.' + fill: + - Задача `parse_batch` дага `orders_ingest` разбирает пачку раз в модельные сутки. + - '`ReplacingMergeTree(snapshot_date)` оставляет версию из последнего принятого слепка.' + links: + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/storage.md#user-content-версии-заказов + - label: DDL таблиц + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/20-ods-tables.sql + - label: DDL представления + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/30-ods-views.sql + + - id: ods_order_errors + name: ods.order_errors_dist + layer: ods + order: 3 + streams: [orders] + what: Сырые строки слепка, которые не прошли строгий прием, с классом ошибки и `_load_id`. + read: 'Читайте `_dist`: представления `_v` нет, а распределенная таблица показывает ошибки с обоих шардов.' + parts: + - '`ods.order_errors_rep` — реплицируемая таблица ошибок на шарде.' + - '`ods.order_errors_dist` — распределенная поверхность чтения и записи.' + fill: + - Задача `parse_batch` дага `orders_ingest` пишет ошибки из той же пачки, что и годные заказы. + - Ошибки живут один месяц и не останавливают прием годных заказов. + links: + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/storage.md#user-content-таблица-ошибок + - label: DDL + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/20-ods-tables.sql + + - id: dds_event + name: dds.event_v + layer: dds + order: 0 + streams: [events] + what: Публичное представление события на языке бизнеса. Его запрос переименовывает поля и расшифровывает категорию устройства. + read: 'Читайте `dds.event_v`: запрос представления возвращает явный список колонок DDS.' + parts: + - '`dds.event_v` — только представление над `ods.event_v`; своей таблицы в DDS нет.' + keys: + - '`event_id` — тот же идентификатор, что `WatchID` в ODS.' + - '`client_id` — тот же посетитель, что `ClientID` в ODS; соединяет событие с сессией и картой идентичностей.' + - '`event_date` — тот же день, что `EventDate` в ODS.' + - '`purchase_id[1]` — тот же заказ, что `order_id` в DDS и DM.' + fill: + - Данные для `dds.event_v` читаются из актуальных событий ODS без копии и отдельного расписания. + links: + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dds/event.md#user-content-форма + - label: DDL + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/50-dds-views.sql + + - id: dds_session + name: dds.session_v + layer: dds + order: 1 + streams: [events] + what: Сессия, которую запрос дага `dds_transform` собирает из последовательных событий одной куки. В источнике готовой сессии нет. + read: 'Читайте `dds.session_v`: запрос представления возвращает деловые и служебные колонки.' + parts: + - '`dds.session_rep` — реплицируемая таблица сессий на шарде.' + - '`dds.session_dist` — распределенная поверхность загрузки и диагностики.' + - '`dds.session_stage_rep` — донор дневных партиций на шарде.' + - '`dds.session_stage_dist` — распределенная поверхность донора.' + - '`dds.session_v` — публичное представление.' + keys: + - '`client_id` — тот же посетитель, что в событии DDS и карте идентичностей.' + - '`session_date` — день, который в `dm.daily_traffic_v` называется `report_date`.' + fill: + - Даг `dds_transform` пересобирает данные прожитых дней при каждом прогоне конвейера. + - Задачи `session.rebuild` и `session.replace` собирают день в доноре и заменяют партицию. + links: + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dds/session.md#user-content-движок-и-наполнение + - label: DDL таблиц + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/45-dds-tables.sql + - label: DDL представления + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/50-dds-views.sql + + - id: dds_order + name: dds.order_v + layer: dds + order: 2 + streams: [orders] + what: Заказ в текущем состоянии на языке бизнеса. Артикулы, количества и цены позиций хранятся в согласованных массивах. + read: 'Читайте `dds.order_v`: запрос представления возвращает данные без таблиц и донора партиций.' + parts: + - '`dds.order_rep` — реплицируемая таблица заказов на шарде.' + - '`dds.order_dist` — распределенная поверхность загрузки и диагностики.' + - '`dds.order_stage_rep` — донор дневных партиций на шарде.' + - '`dds.order_stage_dist` — распределенная поверхность донора.' + - '`dds.order_v` — публичное представление.' + keys: + - '`order_id` — тот же заказ, что в ODS, событии покупки и витрине сверки.' + - '`user_id` — пользователь магазина, который входит в карту идентичностей.' + - '`item_sku` — тот же артикул, что `sku` в `dic.products`.' + - '`order_date` — день, который в витринах называется `report_date` или `order_day`.' + fill: + - Даг `dds_transform` пересобирает изменяемое окно при каждом прогоне конвейера. + - Задачи `order.rebuild` и `order.replace` собирают дни в доноре и заменяют партиции. + links: + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dds/order.md#user-content-движок-и-наполнение + - label: DDL таблиц + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/45-dds-tables.sql + - label: DDL представления + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/50-dds-views.sql + + - id: dds_identity + name: dds.identity_map_v + layer: dds + order: 3 + streams: [events, orders] + what: Известные пары куки трекера и пользователя магазина. Связь появляется после сопоставленной покупки. + read: 'Читайте `dds.identity_map_v`: запрос представления с `FINAL` возвращает актуальные строки полного пересчета.' + parts: + - '`dds.identity_map_rep` — реплицируемая таблица версий пар на шарде.' + - '`dds.identity_map_dist` — распределенная поверхность записи.' + - '`dds.identity_map_v` — точное публичное представление с `FINAL`.' + keys: + - '`client_id` — кука из события и сессии DDS.' + - '`user_id` — пользователь магазина из заказа DDS.' + fill: + - Задача `identity_map` дага `dds_transform` пересчитывает все известные пары после обновления заказов. + - Новая версия полного набора пишется при каждом прогоне; `FINAL` скрывает физические повторы. + links: + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dds/identity.md#user-content-сборка + - label: DDL таблиц + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/45-dds-tables.sql + - label: DDL представления + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/50-dds-views.sql + + - id: dm_daily_traffic + name: dm.daily_traffic_v + layer: dm + order: 0 + streams: [events] + what: Дневные счетчики посетителей и известных пользователей после склейки идентичностей. + read: 'Читайте `dm.daily_traffic_v`: запрос представления с `FINAL` возвращает актуальные строки полного пересчета.' + parts: + - '`dm.daily_traffic_rep` — реплицируемая таблица версий счетчиков.' + - '`dm.daily_traffic_dist` — распределенная поверхность записи.' + - '`dm.daily_traffic_v` — точное публичное представление с `FINAL`.' + keys: + - '`report_date` — тот же день, что `session_date` в сессии DDS.' + fill: + - Задача `daily_traffic` дага `dm_transform` при каждом прогоне пересчитывает всю историю и записывает новую версию. + - '`FINAL` оставляет текущую строку каждого дня.' + links: + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dm.md#user-content-dmdaily_traffic_v + - label: DDL таблиц + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/55-dm-tables.sql + - label: DDL представления + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/60-dm-views.sql + + - id: dm_revenue + name: dm.revenue_daily_v + layer: dm + order: 1 + streams: [orders] + what: Дневная выручка, число заказов и единиц товара по категориям. + read: 'Читайте `dm.revenue_daily_v`: это публичное представление готовой дневной витрины.' + parts: + - '`dm.revenue_daily_rep` — реплицируемая таблица витрины на шарде.' + - '`dm.revenue_daily_dist` — распределенная поверхность загрузки.' + - '`dm.revenue_daily_stage_rep` — донор дневных партиций на шарде.' + - '`dm.revenue_daily_stage_dist` — распределенная поверхность донора.' + - '`dm.revenue_daily_v` — публичное представление.' + keys: + - '`report_date` — тот же день, что `order_date` в заказе DDS.' + - '`product_category` — категория, найденная по `item_sku` заказа и ключу `sku` словаря.' + fill: + - Даг `dm_transform` пересобирает изменяемое окно заказов при каждом прогоне конвейера. + - Задачи `revenue_daily.rebuild` и `revenue_daily.replace` собирают дни в доноре и заменяют партиции. + links: + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dm.md#user-content-dmrevenue_daily_v + - label: DDL таблиц + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/55-dm-tables.sql + - label: DDL представления + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/60-dm-views.sql + + - id: dm_purchase + name: dm.purchase_vs_orders_v + layer: dm + order: 2 + streams: [events, orders] + what: Сверка события покупки с заказом магазина. Одна строка показывает совпадение или класс расхождения. + read: 'Читайте `dm.purchase_vs_orders_v`: в результате запроса отсутствующей стороне соответствует `NULL`.' + parts: + - '`dm.purchase_vs_orders_rep` — реплицируемая таблица сверки на шарде.' + - '`dm.purchase_vs_orders_dist` — распределенная поверхность загрузки.' + - '`dm.purchase_vs_orders_stage_rep` — донор дневных партиций на шарде.' + - '`dm.purchase_vs_orders_stage_dist` — распределенная поверхность донора.' + - '`dm.purchase_vs_orders_v` — публичное представление.' + keys: + - '`order_id` — тот же заказ, что `purchase_id[1]` в событии и `order_id` в заказе DDS.' + - '`order_day` — общий день события покупки и заказа.' + fill: + - Даг `dm_transform` пересобирает изменяемый хвост обоих источников при каждом прогоне конвейера. + - Задачи `purchase_vs_orders.rebuild` и `purchase_vs_orders.replace` собирают дни в доноре и заменяют партиции. + links: + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dm.md#user-content-dmpurchase_vs_orders_v + - label: DDL таблиц + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/55-dm-tables.sql + - label: DDL представления + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/60-dm-views.sql + + - id: dm_dq + name: dm.dq_summary_v + layer: dm + order: 3 + streams: [events, orders] + what: Текущее состояние пяти атомарных проверок качества по дням. История запусков остается в Airflow. + read: 'Читайте `dm.dq_summary_v`: запрос к публичному представлению возвращает последнюю опубликованную сводку дня.' + parts: + - '`dm.dq_summary_rep` — реплицируемая таблица опубликованной сводки.' + - '`dm.dq_summary_dist` — распределенная поверхность чтения и записи.' + - '`dm.dq_summary_stage_rep` — общий донор дневных партиций.' + - '`dm.dq_summary_stage_dist` — распределенная поверхность донора.' + - '`dm.dq_summary_v` — публичное представление.' + fill: + - Даг `dq_check` при каждом прогоне сначала выполняет все пять проверок, затем заменяет готовые дневные партиции. + - '`sessions_vs_reference` — `sql/dq/sessions_vs_reference.sql`.' + - '`classes_vs_inventory` — `sql/dq/classes_vs_inventory.sql` и паспорт мира.' + - '`revenue_daily_vs_dds` — `sql/dq/revenue_daily_vs_dds.sql`.' + - '`purchase_vs_orders_vs_dds` — `sql/dq/purchase_vs_orders_vs_dds.sql`.' + - '`daily_traffic_vs_dds` — `sql/dq/daily_traffic_vs_dds.sql`.' + links: + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dm.md#user-content-dmdq_summary + - label: DDL таблиц + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/55-dm-tables.sql + - label: DDL представления + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/60-dm-views.sql + + - id: dic_products + name: dic.products + layer: dic + order: 0 + streams: [orders] + what: Словарь товаров вне цепочки слоев. По артикулу доступны название, бренд, категория и цена. + read: 'Читайте `dic.products`: у словаря одна поверхность без суффикса.' + parts: + - '`dic.products_file` — чтец CSV-каталога из файла репозитория.' + - '`dic.products` — словарь в памяти поверх подложки.' + keys: + - '`sku` — тот же артикул, что `item_sku` в позиции заказа DDS.' + fill: + - ClickHouse перечитывает подложку каждые 60–90 секунд по настройке `LIFETIME`. + links: + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/storage.md#user-content-справочники + - label: DDL + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/05-dic-catalog.sql + +edges: + - id: hits_to_stg + from: kafka_hits + to: stg_hits + mechanism: materialized_view + streams: [events] + rhythm: непрерывно, по мере прихода сообщений + actor: Чтец `stg.hits_raw_kafka` и матвью `stg.hits_raw_mv`. + links: + - label: SQL + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/40-stg-views.sql + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/storage.md#user-content-при%D1%91м-поток-и-его-свойства + + - id: stg_to_event + from: stg_hits + to: ods_event + mechanism: materialized_view + streams: [events] + rhythm: непрерывно, по мере прихода сообщений + actor: Матвью `ods.event_mv` разбирает годные строки. + links: + - label: SQL + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/30-ods-views.sql + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/storage.md#user-content-при%D1%91м-поток-и-его-свойства + + - id: stg_to_event_errors + from: stg_hits + to: ods_event_errors + mechanism: materialized_view + streams: [events] + rhythm: непрерывно, по мере прихода сообщений + actor: Матвью `ods.event_errors_mv` отбирает строки, которые не прошли строгий прием. + links: + - label: SQL + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/30-ods-views.sql + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/storage.md#user-content-таблица-ошибок + + - id: orders_to_stg + from: kafka_orders + to: stg_orders + mechanism: airflow_dag + streams: [orders] + rhythm: пачкой, раз в модельные сутки + actor: Задача `pull_batch` дага `orders_ingest` читает `stg.orders_raw_kafka`. + links: + - label: SQL + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/stg/orders_raw_load.sql + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/orders/ingestion.md#user-content-одно-чтение-kafka + + - id: stg_to_order + from: stg_orders + to: ods_order + mechanism: airflow_dag + streams: [orders] + rhythm: пачкой, раз в модельные сутки + actor: Задача `parse_batch` дага `orders_ingest` пишет годные версии заказа. + links: + - label: SQL + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ods/order_parse.sql + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/orders/ingestion.md#user-content-граница-строгого-при%D1%91ма + + - id: stg_to_order_errors + from: stg_orders + to: ods_order_errors + mechanism: airflow_dag + streams: [orders] + rhythm: пачкой, раз в модельные сутки + actor: Задача `parse_batch` дага `orders_ingest` пишет негодные строки той же пачки. + links: + - label: SQL + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ods/order_parse.sql + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/orders/ingestion.md#user-content-граница-строгого-при%D1%91ма + + - id: ods_to_dds_event + from: ods_event + to: dds_event + mechanism: view + streams: [events] + rhythm: без копии, данные читаются на месте + actor: Запрос `dds.event_v` читает данные из `ods.event_v` и переименовывает поля на языке бизнеса. + links: + - label: SQL + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/ddl/50-dds-views.sql + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dds/event.md#user-content-форма + + - id: event_to_session + from: dds_event + to: dds_session + mechanism: airflow_dag + streams: [events] + rhythm: пачкой, при каждом прогоне `dds_transform` + actor: Задачи `session.rebuild` и `session.replace` дага `dds_transform`. + links: + - label: SQL сборки + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/dds/session_rebuild.sql + - label: SQL замены + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/dds/session_replace.sql + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dds/session.md#user-content-движок-и-наполнение + + - id: order_to_dds + from: ods_order + to: dds_order + mechanism: airflow_dag + streams: [orders] + rhythm: пачкой, при каждом прогоне `dds_transform` + actor: Задачи `order.rebuild` и `order.replace` дага `dds_transform`. + links: + - label: SQL сборки + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/dds/order_rebuild.sql + - label: SQL замены + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/dds/order_replace.sql + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dds/order.md#user-content-движок-и-наполнение + + - id: event_to_identity + from: dds_event + to: dds_identity + mechanism: airflow_dag + streams: [events] + rhythm: пачкой, при каждом прогоне `dds_transform` + actor: Задача `identity_map` дага `dds_transform` соединяет покупки с заказами. + links: + - label: SQL + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/dds/identity_map_rebuild.sql + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dds/identity.md#user-content-сборка + + - id: order_to_identity + from: dds_order + to: dds_identity + mechanism: airflow_dag + streams: [orders] + rhythm: пачкой, при каждом прогоне `dds_transform` + actor: Задача `identity_map` дага `dds_transform` соединяет заказы с покупками. + links: + - label: SQL + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/dds/identity_map_rebuild.sql + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dds/identity.md#user-content-сборка + + - id: session_to_traffic + from: dds_session + to: dm_daily_traffic + mechanism: airflow_dag + streams: [events] + rhythm: пачкой, при каждом прогоне `dm_transform` + actor: Задача `daily_traffic` дага `dm_transform` считает посетителей по сессиям. + links: + - label: SQL + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/dm/daily_traffic_rebuild.sql + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dm.md#user-content-dmdaily_traffic_v + + - id: identity_to_traffic + from: dds_identity + to: dm_daily_traffic + mechanism: airflow_dag + streams: [events] + rhythm: пачкой, при каждом прогоне `dm_transform` + actor: Задача `daily_traffic` дага `dm_transform` считает известных пользователей по `client_id`. + links: + - label: SQL + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/dm/daily_traffic_rebuild.sql + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dm.md#user-content-dmdaily_traffic_v + + - id: order_to_revenue + from: dds_order + to: dm_revenue + mechanism: airflow_dag + streams: [orders] + rhythm: пачкой, при каждом прогоне `dm_transform` + actor: Задачи `revenue_daily.rebuild` и `revenue_daily.replace` дага `dm_transform`. + links: + - label: SQL сборки + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/dm/revenue_daily_rebuild.sql + - label: SQL замены + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/dm/revenue_daily_replace.sql + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dm.md#user-content-dmrevenue_daily_v + + - id: products_to_revenue + from: dic_products + to: dm_revenue + mechanism: airflow_dag + streams: [orders] + rhythm: пачкой, при каждом прогоне `dm_transform` + actor: Даг `dm_transform` получает категорию через `dictGet` по `item_sku`; ключ словаря — `sku`. + links: + - label: SQL + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/dm/revenue_daily_rebuild.sql + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dm.md#user-content-dmrevenue_daily_v + + - id: event_to_purchase + from: dds_event + to: dm_purchase + mechanism: airflow_dag + streams: [events] + rhythm: пачкой, при каждом прогоне `dm_transform` + actor: Задачи `purchase_vs_orders.rebuild` и `purchase_vs_orders.replace` дага `dm_transform` читают покупки. + links: + - label: SQL сборки + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/dm/purchase_vs_orders_rebuild.sql + - label: SQL замены + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/dm/purchase_vs_orders_replace.sql + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dm.md#user-content-dmpurchase_vs_orders_v + + - id: order_to_purchase + from: dds_order + to: dm_purchase + mechanism: airflow_dag + streams: [orders] + rhythm: пачкой, при каждом прогоне `dm_transform` + actor: Задачи `purchase_vs_orders.rebuild` и `purchase_vs_orders.replace` дага `dm_transform` читают заказы. + links: + - label: SQL сборки + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/dm/purchase_vs_orders_rebuild.sql + - label: SQL замены + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/sql/dm/purchase_vs_orders_replace.sql + - label: Справочник + url: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/architecture/dm.md#user-content-dmpurchase_vs_orders_v diff --git a/docs/handbook/architecture-map/render.py b/docs/handbook/architecture-map/render.py new file mode 100644 index 0000000..2398ccb --- /dev/null +++ b/docs/handbook/architecture-map/render.py @@ -0,0 +1,54 @@ +# /// script +# requires-python = ">=3.13" +# dependencies = ["pyyaml"] +# /// + +"""Собрать интерактивную карту хранилища из YAML и HTML-шаблона.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +import yaml + +ROOT = Path(__file__).parent +SOURCE = ROOT / "map.yaml" +TEMPLATE = ROOT / "template.html" +OUTPUT = ROOT.parent / "architecture-map.html" +MARKER = "/*__MAP_DATA__*/" +NOTICE = ( + "\n" +) + + +def render() -> str: + """Подставить данные карты в шаблон без изменения порядка ключей.""" + data = yaml.safe_load(SOURCE.read_text(encoding="utf-8")) + payload = json.dumps(data, ensure_ascii=False, indent=2) + template = TEMPLATE.read_text(encoding="utf-8") + return NOTICE + template.replace(MARKER, payload) + + +def main() -> int: + """Записать карту или проверить, что готовый файл не устарел.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--check", action="store_true", help="проверить готовый файл") + args = parser.parse_args() + rendered = render() + if args.check: + if not OUTPUT.exists() or OUTPUT.read_text(encoding="utf-8") != rendered: + print(f"{OUTPUT} устарел; пересоберите карту") + return 1 + print(f"{OUTPUT} соответствует исходникам") + return 0 + OUTPUT.write_text(rendered, encoding="utf-8") + print(f"собрана карта: {OUTPUT}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/docs/handbook/architecture-map/template.html b/docs/handbook/architecture-map/template.html new file mode 100644 index 0000000..0a018ce --- /dev/null +++ b/docs/handbook/architecture-map/template.html @@ -0,0 +1,453 @@ + + + + + + Карта слоев хранилища + + + +
+

+

+
+
+
+
+
+
+
+ +
+ +
+ + + diff --git a/docs/handbook/storage-layers.md b/docs/handbook/storage-layers.md index 26624b1..3494bfe 100644 --- a/docs/handbook/storage-layers.md +++ b/docs/handbook/storage-layers.md @@ -132,6 +132,8 @@ ORDER BY revenue DESC; ## Куда дальше +Откройте [интерактивную карту](architecture-map.html) из своей копии +репозитория и проследите путь данных. Список объектов, сроки хранения и устройство таблиц — в [справочнике хранилища](../architecture/storage.md). Как данные переносят между слоями — в следующей главе: [потоки Airflow и даги модельного