From ebe3de4ae819e8cf16c43d5867a8555f4022143e Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Tue, 25 Aug 2026 03:22:35 -0400 Subject: [PATCH 1/2] =?UTF-8?q?docs(etl):=20=D1=81=D0=BF=D1=80=D0=BE=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20=D0=B4=D0=BD?= =?UTF-8?q?=D0=B5=D0=B2=D0=BD=D0=BE=D0=B9=20=D0=BA=D0=BE=D0=BD=D0=B2=D0=B5?= =?UTF-8?q?=D0=B9=D0=B5=D1=80=20=D1=82=D1=80=D0=B0=D0=BD=D1=81=D1=84=D0=BE?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D1=86=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Зачем: развилка #134 карты #127 — чем и когда гоняются трансформации DDS и витрины-таблицы, пока спека числила etl_pipeline за этапом 5. Что: дока docs/architecture/etl.md — даг etl_pipeline рождается этапом 4: дёргает dds_transform и dm_transform последовательно (DQ — будущее звено); живой день зовёт его с ожиданием после установки позиции, после разгона пачкой менти запускает рукой, world_initialize — последним шагом; параметров нет, объём выводится из world_position и состояния слоёв; первый шаг — ожидание хвоста hits; механика REPLACE PARTITION — этап 4, этапу 5 — партиционная переобработка. Указатели в доках заказа, сессии, README слоя и ретро-пометки в ADR 0008/0009 переведены на новую доку. Проверка: вычитка; резолюция и доводы — в тикете #134. Co-Authored-By: Claude Fable 5 --- docs/adr/0008-order-ingestion.md | 5 +- docs/adr/0009-world-control.md | 5 +- docs/architecture/dds/README.md | 4 +- docs/architecture/dds/order.md | 6 +- docs/architecture/dds/session.md | 6 +- docs/architecture/etl.md | 101 +++++++++++++++++++++++++++++++ 6 files changed, 117 insertions(+), 10 deletions(-) create mode 100644 docs/architecture/etl.md diff --git a/docs/adr/0008-order-ingestion.md b/docs/adr/0008-order-ingestion.md index 0f2a936..1e41e51 100644 --- a/docs/adr/0008-order-ingestion.md +++ b/docs/adr/0008-order-ingestion.md @@ -141,7 +141,10 @@ Этап 3 забирает у этапа 5 первый настоящий даг. Раздел 9 мастер-спеки отдавал даги этапу 5 целиком; приём заказов без дага не существует, поэтому порядок -меняется. `etl_pipeline` остаётся за этапом 5. +меняется. `etl_pipeline` остаётся за этапом 5. Позднее сдвинулся и он: +резолюция #134 родила его этапом 4 — [дневным конвейером +трансформаций](../architecture/etl.md), — а этапу 5 оставила партиционную +переобработку. Расхождения с мастер-спекой, внесённые тем же коммитом: раздел 6 (чтец заказов живёт на одной ноде и без матвью), раздел 7 (развилка закрыта, у `orders` одна diff --git a/docs/adr/0009-world-control.md b/docs/adr/0009-world-control.md index 8ad13b4..07766e6 100644 --- a/docs/adr/0009-world-control.md +++ b/docs/adr/0009-world-control.md @@ -135,7 +135,10 @@ Этапы 3 и 5 зовут генератор тем же приёмом: пакетный забор слепка заказов ([ADR 0008](0008-order-ingestion.md)) прирастает к тому же работнику, а `etl_pipeline` этапа 5 получает готовую обвязку `DockerOperator` и готовое -правило позиции. +правило позиции. Позднее резолюция #134 родила `etl_pipeline` этапом 4 — +[дневным конвейером трансформаций](../architecture/etl.md) без генераторных +шагов: обвязка и правило позиции остаются работникам, а конвейер живой день +дёргает после установки позиции. Расхождения, внесённые тем же коммитом: раздел 8 спеки генератора (позицию двигал «только даг `next_day`»; теперь — всякий, кто сыграл день, значением diff --git a/docs/architecture/dds/README.md b/docs/architecture/dds/README.md index 52d4b62..6308b32 100644 --- a/docs/architecture/dds/README.md +++ b/docs/architecture/dds/README.md @@ -51,11 +51,11 @@ - Служебные колонки — координаты собственной загрузки слоя; чужие ярлыки не переносятся, а спрашиваются у их слоя по ключу сущности. Вывод правила и цепочка расследования — [модель заказа](order.md). +- Наполняет слой [дневной конвейер](../etl.md): запуск, объём прогона и + замена партиций на кластере — его устройство. ## Открытые решения -- Запуск трансформаций и механика замены партиций на кластере — #134 и - этап 5. - Витрины DM — #135. ## Родословная diff --git a/docs/architecture/dds/order.md b/docs/architecture/dds/order.md index 44463f6..a03362d 100644 --- a/docs/architecture/dds/order.md +++ b/docs/architecture/dds/order.md @@ -56,7 +56,7 @@ ODS: это единственный устойчивый ключ сущнос объектов». Механика запуска — где собирается партиция и как подменяется на кластере — -сюда не входит: это устройство трансформаций (#134, этап 5). +сюда не входит: это устройство [дневного конвейера](../etl.md). ## Служебные колонки @@ -95,8 +95,8 @@ ODS: это единственный устойчивый ключ сущнос ## Не входит -- Механика запуска трансформации и замены партиций на кластере — #134 и - этап 5. +- Механика запуска трансформации и замены партиций на кластере — + [дневной конвейер](../etl.md). - Контур проверок качества (даг DQ, `dm.dq_summary`) — отдельный разговор этапа 4. - Инкрементальное наполнение вместо пересборки — техдолг #2. diff --git a/docs/architecture/dds/session.md b/docs/architecture/dds/session.md index 70cd96f..0099260 100644 --- a/docs/architecture/dds/session.md +++ b/docs/architecture/dds/session.md @@ -91,7 +91,7 @@ Источник сборки — `dds.event_v` ([контракт события](event.md)): сессии нужны от него ключи нарезки, тип события, страница, UTM и паспорт куки. Механика запуска — где собирается партиция и как подменяется на кластере — -не входит: это устройство трансформаций (#134, этап 5). +не входит: это устройство [дневного конвейера](../etl.md). ## Сверка с эталоном @@ -115,8 +115,8 @@ ## Не входит -- Механика запуска трансформации и замены партиций на кластере — #134 и - этап 5. +- Механика запуска трансформации и замены партиций на кластере — + [дневной конвейер](../etl.md). - Контур проверок качества (даг DQ, `dm.dq_summary`) — отдельный разговор этапа 4. - Форма витрин над сессиями — #135. diff --git a/docs/architecture/etl.md b/docs/architecture/etl.md new file mode 100644 index 0000000..85f9047 --- /dev/null +++ b/docs/architecture/etl.md @@ -0,0 +1,101 @@ +# Дневной конвейер трансформаций + +Как принятые источники превращаются в модель DDS и витрины DM: кто запускает +трансформации, когда и в каком объёме. Правила самих сущностей — зерно, +движки, что именно пересобирается — живут в доках слоя +([карта модели DDS](dds/README.md)); здесь — только конвейер. + +Спека обещала `etl_pipeline` этапом 5 — он понадобился раньше. Конвейер +рождается этапом 4, и временного в нём ничего нет: дневная сборка вперёд +останется и после. Этапу 5 достаётся его законный предмет — партиционная +переобработка, умение переиграть произвольный прошлый день по команде. + +## Форма + +Дневной конвейер — даг `etl_pipeline`, единственный на стенде. Своей работы +у него нет: он дёргает слойные даги последовательно и ждёт каждый — +`dds_transform`, затем `dm_transform`. Контур DQ этапа 4 встанет его же +звеном после DM; состав звена — предмет развилки витрин (#135) и нарезки. + +Слойные даги устроены по образцу `orders_ingest`: без расписания, один +активный прогон, запускаются триггером конвейера или рукой при разборе +происшествий. Витрины внутри `dm_transform` — отдельные задачи. + +## Три пути запуска + +- **Живой день.** `world_live_day` дёргает конвейер с ожиданием — последним + шагом, после установки позиции. День сыгран — факт, упавшая трансформация + его не отменяет, поэтому позиция уже стоит; но зеленеть, не узнав, + разложился ли день, работник не должен ([ADR 0003](../adr/0003-dag-run-state.md)). + Такой отказ чинится перезапуском `etl_pipeline` рукой — перезапуску ничего + не нужно помнить, см. «Объём прогона». +- **Разгон пачкой.** `world_next_day` конвейер не дёргает: пульт управляет + генератором и только им. После разгона менти запускает конвейер сам — + и видит, как слои догоняют уехавший вперёд мир. +- **Рождение мира.** `world_initialize` дёргает конвейер последним шагом: + `make up` отдаёт стенд с собранными слоями, а не пустые витрины. Уроки + ручного запуска начинаются со следующих дней. + +## Объём прогона + +Параметров у конвейера нет: объём он выводит сам из `world_position` и +состояния слоёв. Сессии догоняют хвост — от последнего собранного дня до +последнего прожитого; заказы пересобирают дышащее окно K от позиции; карта +идентичностей пересчитывается целиком; правила витрин — у витрин (#135). +Поэтому один и тот же запуск одинаково верен из триггера, с кнопки менти и +после падения, а разгону на семь дней хватает одного прогона. + +## Готовность источника + +Первый шаг `dds_transform` — дождаться, пока хвост топика `hits` дожёван: +отставание его потребителей нулевое (`system.kafka_consumers`). Зелёный +приём заказов о событиях не говорит ничего — топики разные и потребители +разные. В живом режиме зазор ничтожен, но в пакетном генератор выстреливает +дни в топик быстрее, чем чтец их прожёвывает, и сессии собрались бы по +недожёванному дню. DQ-сверка такой день подсветит, но починить его до +этапа 5 нечем: профилактика дешевле ремонта, которого ещё нет. + +## Идемпотентность + +Партиция не заменяется простой вставкой. День собирается в таблицу-двойник — +вставка через Distributed раскладывает строки по шардам, — затем +`REPLACE PARTITION` на кластере атомарно подменяет кусок каждого шарда. Эта +механика — часть самих трансформаций и строится этапом 4: без неё дневной +прогон не повторяем. + +## Не входит + +- Партиционная переобработка — переиграть произвольный прошлый день или + диапазон, включая форму необязательных параметров того же дага, — этап 5 + (#7); туда же стык #111 про дату модельного мира и `logical_date`. +- Инкрементальный ETL вместо пересборки — техдолг #2. +- Состав витрин `dm_transform` и звена DQ — #135 и нарезка этапа 4. + +## Отклонённые варианты + +- **Задачи трансформаций прямо в работниках пульта.** Один зелёный прогон — + весь день, но пульт — про ось времени: работников два, шаги дублировались + бы, а этапу 5 пришлось бы выдирать их обратно. +- **Make-цель или SQL рукой при приёмке.** Второй оркестратор рядом с + Airflow — против [ADR 0013](../adr/0013-world-lifecycle.md), — а в живом + режиме слои копили бы отставание молча. +- **Временный даг с нейтральным именем до этапа 5.** Дневная сборка вперёд + никуда не денется, переезд был бы переименованием ради выдуманной границы. +- **Диапазон дней параметрами триггера.** Ручному запуску менти параметры + передавать некому, а перезапуск после падения требовал бы помнить conf + упавшего прогона. +- **Принять гонку с хвостом `hits`.** Дыра всплыла бы в DQ-сверке, а + чиниться — не раньше этапа 5. + +## Связанные решения + +- [ADR 0003](../adr/0003-dag-run-state.md) — зелёный конец графа не + переживает отказ выше: отсюда ожидания в триггерах. +- [ADR 0009](../adr/0009-world-control.md) — пульт мира: работники, которые + дёргают конвейер живого дня. +- [ADR 0013](../adr/0013-world-lifecycle.md) — жизненный цикл мира: почему + прикладным процессом владеет Airflow и почему `world_initialize` кончается + собранными слоями. +- Резолюция — тикет #134 карты #127; что пересобирает каждая сущность — + [заказ](dds/order.md), [сессия](dds/session.md), + [карта идентичностей](dds/identity.md). -- 2.54.0 From d1aad3b1434ade71f60731feb6c95963c545a147 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Tue, 25 Aug 2026 03:25:08 -0400 Subject: [PATCH 2/2] =?UTF-8?q?docs(etl):=20=D0=B2=D1=8B=D1=87=D0=B8=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=20=D0=B4=D0=BD=D0=B5=D0=B2=D0=BD=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D0=B2=D0=B5=D0=B9=D0=B5=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Зачем: горячее ревью доки перед слиянием PR #146. Что: «единственный на стенде» больше не читается как «единственный даг стенда»; в пункте про живой день причина порядка триггера названа один раз и по делу; конвейер дёргает работник живого дня, а не работники; в ретро-пометке ADR 0009 расставлены кто кого дёргает. Проверка: перечитка диффа. Co-Authored-By: Claude Fable 5 --- docs/adr/0009-world-control.md | 4 ++-- docs/architecture/etl.md | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/adr/0009-world-control.md b/docs/adr/0009-world-control.md index 07766e6..3757858 100644 --- a/docs/adr/0009-world-control.md +++ b/docs/adr/0009-world-control.md @@ -137,8 +137,8 @@ `etl_pipeline` этапа 5 получает готовую обвязку `DockerOperator` и готовое правило позиции. Позднее резолюция #134 родила `etl_pipeline` этапом 4 — [дневным конвейером трансформаций](../architecture/etl.md) без генераторных -шагов: обвязка и правило позиции остаются работникам, а конвейер живой день -дёргает после установки позиции. +шагов: обвязка и правило позиции остаются работникам, а живой день дёргает +конвейер после установки позиции. Расхождения, внесённые тем же коммитом: раздел 8 спеки генератора (позицию двигал «только даг `next_day`»; теперь — всякий, кто сыграл день, значением diff --git a/docs/architecture/etl.md b/docs/architecture/etl.md index 85f9047..41a4d42 100644 --- a/docs/architecture/etl.md +++ b/docs/architecture/etl.md @@ -12,7 +12,7 @@ ## Форма -Дневной конвейер — даг `etl_pipeline`, единственный на стенде. Своей работы +Дневной конвейер на стенде один — даг `etl_pipeline`. Своей работы у него нет: он дёргает слойные даги последовательно и ждёт каждый — `dds_transform`, затем `dm_transform`. Контур DQ этапа 4 встанет его же звеном после DM; состав звена — предмет развилки витрин (#135) и нарезки. @@ -24,9 +24,10 @@ ## Три пути запуска - **Живой день.** `world_live_day` дёргает конвейер с ожиданием — последним - шагом, после установки позиции. День сыгран — факт, упавшая трансформация - его не отменяет, поэтому позиция уже стоит; но зеленеть, не узнав, - разложился ли день, работник не должен ([ADR 0003](../adr/0003-dag-run-state.md)). + шагом, после установки позиции. Порядок несёт смысл: день сыгран — факт, + упавшая трансформация его не отменяет, поэтому позиция ставится раньше; но + зеленеть, не узнав, разложился ли день, работник не должен + ([ADR 0003](../adr/0003-dag-run-state.md)). Такой отказ чинится перезапуском `etl_pipeline` рукой — перезапуску ничего не нужно помнить, см. «Объём прогона». - **Разгон пачкой.** `world_next_day` конвейер не дёргает: пульт управляет @@ -91,8 +92,8 @@ - [ADR 0003](../adr/0003-dag-run-state.md) — зелёный конец графа не переживает отказ выше: отсюда ожидания в триггерах. -- [ADR 0009](../adr/0009-world-control.md) — пульт мира: работники, которые - дёргают конвейер живого дня. +- [ADR 0009](../adr/0009-world-control.md) — пульт мира: его работник + живого дня и дёргает конвейер. - [ADR 0013](../adr/0013-world-lifecycle.md) — жизненный цикл мира: почему прикладным процессом владеет Airflow и почему `world_initialize` кончается собранными слоями. -- 2.54.0