Модель заказа в DDS: dds.order и пересборка окна #151

Closed
opened 2026-08-25 15:00:56 +03:00 by ddmitry · 2 comments
Owner

Part of #6.

Цель

Физическая модель заказа: dds.order_rep / _dist / _v и первая
трансформация слоя — пересборка «дышащих» дней из ods.order_v заменой
партиций. Здесь же рождаются даг dds_transform и механика замены
партиции на кластере — образец для сессий и витрин. Учебный результат
(order.md): идемпотентность, устроенная иначе, чем в приёме, — заменой
партиции, а не версиями.

Что войдёт

  • DDL по order.md и конвенциям storage.md: dds.order_rep —
    ReplicatedMergeTree без колонки версии, партиция order_date, ключ
    сортировки order_id; dds.order_dist — шардирование
    cityHash64(order_id); dds.order_v. Отдельный грант не нужен:
    bi_reader уже читает dds.* по ADR 0007.
  • Состав строки, времена в поясе счётчика, order_date и
    позиции-массивы — по order.md; служебные _load_id / _load_ts
    собравшего запуска.
  • Трансформация в sql/dds/ по конвенции раскладки SQL. Объём — дышащие
    дни плюс дни, которых в слое ещё нет: первая сборка собирает весь
    стартовый мир (правило etl.md — объём из позиции и состояния слоёв).
    Дышащие дни не выводить арифметикой «K дней от world_position»:
    слепки запаздывают на день, и такой счёт промахивается по краям окна;
    дышащие дни — ровно те, которые принёс последний принятый слепок,
    вывод множества зафиксировать комментарием в SQL. Сбор — в таблицу-двойник, затем REPLACE PARTITION
    ON CLUSTER по локальным таблицам (storage.md, «Что проверено»: по
    Distributed операции с партициями отвечают кодом 48); форма
    двойника — правило партиции-донора; вставка в _dist — с осознанным
    parallel_distributed_insert_select (капкан #137).
  • Окно K приходит на сторону стенда: сейчас константа живёт только в
    генераторе (ORDER_WINDOW_DAYS); K — константа мира, не параметр
    (спека §9, срез 3); где ей жить на стороне дагов — решить здесь.
  • Даг dds_transform по образцу orders_ingest: без расписания, один
    активный прогон, первая задача — заказы, имя задачи — имя сущности;
    параметров нет.
  • Спорный DDL — сверить через MCP Context7, решение зафиксировать
    комментарием. Таблица DDL-файлов в storage.md — тем же PR.

Границы

  • Сессии, карта идентичностей, витрины — свои тикеты; etl_pipeline и
    пути запуска — тикет конвейера: пока dds_transform запускается рукой.
  • Переобработка произвольного прошлого дня — этап 5 (#7); инкремент —
    техдолг #2.

Кому что

Модельный DDL и SQL пересборки — Opus. Codex — опыты по брифу: повтор
прогона, «дыхание» статусов внутри окна, уборка за опытами (testing.md).

Сначала прочитать

  • docs/architecture/dds/order.md — целиком, это спека тикета;
  • docs/architecture/etl.md — «Объём прогона», «Идемпотентность»;
  • docs/architecture/storage.md — «Раскладка по шардам» (капкан вставки,
    правило донора), «Служебные колонки», «Раскладка SQL», «Что проверено»;
  • docs/architecture/orders/ingestion.md — что обещает ods.order_v.

Проверка

  • make lint; на стенде make smoke, make check-clickhouse и
    make check-services (родился даг) зелёные.
  • Прогон по стартовому миру: заказы в dds.order_v есть за все дни
    мира, счёт равен ods.order_v (обе стороны — по Distributed); каждый
    заказ — на шарде своего cityHash64(order_id).
  • Позиции разобраны честно: у каждого заказа сумма «цена × количество»
    по массивам равна items_total — инвариант бэкенда.
  • Повтор прогона без нового слепка: деловое содержимое dds.order_v
    не изменилось, _load_id пересобранных дней — новый.
  • После нового слепка: пересобраны ровно дни, которые слепок нёс (их
    _load_id новый), прочие дни не тронуты (их _load_id прежний).
  • Запрос ролью bi: dds.order_v, _rep и _dist проходят;
    чтение STG отклоняется.
Part of #6. ## Цель Физическая модель заказа: `dds.order_rep` / `_dist` / `_v` и первая трансформация слоя — пересборка «дышащих» дней из `ods.order_v` заменой партиций. Здесь же рождаются даг `dds_transform` и механика замены партиции на кластере — образец для сессий и витрин. Учебный результат (order.md): идемпотентность, устроенная иначе, чем в приёме, — заменой партиции, а не версиями. ## Что войдёт - DDL по order.md и конвенциям storage.md: `dds.order_rep` — `ReplicatedMergeTree` без колонки версии, партиция `order_date`, ключ сортировки `order_id`; `dds.order_dist` — шардирование `cityHash64(order_id)`; `dds.order_v`. Отдельный грант не нужен: `bi_reader` уже читает `dds.*` по ADR 0007. - Состав строки, времена в поясе счётчика, `order_date` и позиции-массивы — по order.md; служебные `_load_id` / `_load_ts` собравшего запуска. - Трансформация в `sql/dds/` по конвенции раскладки SQL. Объём — дышащие дни плюс дни, которых в слое ещё нет: первая сборка собирает весь стартовый мир (правило etl.md — объём из позиции и состояния слоёв). Дышащие дни не выводить арифметикой «K дней от `world_position`»: слепки запаздывают на день, и такой счёт промахивается по краям окна; дышащие дни — ровно те, которые принёс последний принятый слепок, вывод множества зафиксировать комментарием в SQL. Сбор — в таблицу-двойник, затем `REPLACE PARTITION` ON CLUSTER по локальным таблицам (storage.md, «Что проверено»: по `Distributed` операции с партициями отвечают кодом 48); форма двойника — правило партиции-донора; вставка в `_dist` — с осознанным `parallel_distributed_insert_select` (капкан #137). - Окно K приходит на сторону стенда: сейчас константа живёт только в генераторе (`ORDER_WINDOW_DAYS`); K — константа мира, не параметр (спека §9, срез 3); где ей жить на стороне дагов — решить здесь. - Даг `dds_transform` по образцу `orders_ingest`: без расписания, один активный прогон, первая задача — заказы, имя задачи — имя сущности; параметров нет. - Спорный DDL — сверить через MCP Context7, решение зафиксировать комментарием. Таблица DDL-файлов в storage.md — тем же PR. ## Границы - Сессии, карта идентичностей, витрины — свои тикеты; `etl_pipeline` и пути запуска — тикет конвейера: пока `dds_transform` запускается рукой. - Переобработка произвольного прошлого дня — этап 5 (#7); инкремент — техдолг #2. ## Кому что Модельный DDL и SQL пересборки — Opus. Codex — опыты по брифу: повтор прогона, «дыхание» статусов внутри окна, уборка за опытами (testing.md). ## Сначала прочитать - docs/architecture/dds/order.md — целиком, это спека тикета; - docs/architecture/etl.md — «Объём прогона», «Идемпотентность»; - docs/architecture/storage.md — «Раскладка по шардам» (капкан вставки, правило донора), «Служебные колонки», «Раскладка SQL», «Что проверено»; - docs/architecture/orders/ingestion.md — что обещает `ods.order_v`. ## Проверка - [x] `make lint`; на стенде `make smoke`, `make check-clickhouse` и `make check-services` (родился даг) зелёные. - [x] Прогон по стартовому миру: заказы в `dds.order_v` есть за все дни мира, счёт равен `ods.order_v` (обе стороны — по Distributed); каждый заказ — на шарде своего `cityHash64(order_id)`. - [x] Позиции разобраны честно: у каждого заказа сумма «цена × количество» по массивам равна `items_total` — инвариант бэкенда. - [x] Повтор прогона без нового слепка: деловое содержимое `dds.order_v` не изменилось, `_load_id` пересобранных дней — новый. - [x] После нового слепка: пересобраны ровно дни, которые слепок нёс (их `_load_id` новый), прочие дни не тронуты (их `_load_id` прежний). - [x] Запрос ролью `bi`: `dds.order_v`, `_rep` и `_dist` проходят; чтение STG отклоняется.
ddmitry added a new dependency 2026-08-25 15:01:22 +03:00
Author
Owner

Готово, ветка 151-dds-order-model.

Что сделано

  • sql/ddl/45-dds-tables.sql — dds.order_rep / _dist и двойник
    dds.order_stage_rep / _dist, донор партиций; dds.order_v добавлено в
    50-dds-views.sql.
  • sql/dds/ — три запроса: объём прогона, сборка в двойник, замена партиции.
  • dags/dds_transform.py — группа задач order: объём, сборка, замена по дню
    размноженной задачей.
  • Окно K пришло на сторону стенда якорем в compose.yaml.

Решения по развилкам тикета

Вывод дышащих дней. Отбор по snapshot_date строк отвергнут: у
неизменившегося заказа ярлык выжившей версии — жребий. Замер на стартовом
мире: последний слепок нёс все 1662 заказа, ярлык последнего слепка сохранили
1188. Окно считается от позднейшего дня заказа в ODS плюс K; вывод и замер — в
sql/dds/order_scope.sql и etl.md.

Где живёт K. Копией в compose.yaml, владелец числа — generator/world.py.
Опись мира как единый источник отвергнута: хранилище не должно читать артефакт
сборки источника. Риск дрейфа назван и передан контуру DQ.

Двойник. Рядом с целью в dds, отдельная база не заводилась: storage.md
уже держит позицию «право доступа шире договора».

Ревью

Слепое ревью Codex (свежий тред, 100 команд, все шесть критериев перепроверены
независимо): шесть находок, CHANGES_REQUIRED. Исправлено четыре, две приняты
с доводом.

Главная — прогресс слоя считался по max(order_date), и упавшая замена
оставляла дыру, которую следующий прогон не видел. Проверено на живых данных:
партиция 2026-06-01 снесена руками, старая формула отдала 06-02 … 06-08
(дыра навсегда), новая — 06-01 … 06-08; после прогона день восстановился
побайтово, отпечаток 16124F97EAE696C9 совпал, _load_id новый.

Остаток той же находки — половинчатая замена по шардам, когда ON CLUSTER
прошёл на одном шарде из двух, — сюда не чинится: день в слое присутствует, и
отличить его может только сверка с источником. Вместе с дрейфом K, сдвигом
края окна на пустом дне и молчаливыми нулями в items это записано в
etl.md, раздел «Чего конвейер о себе не знает», и адресовано контуру DQ.

Хвост

Заведён #163: координаты доставки в ODS — ярлык, которому нельзя верить.
Три из шести находок ревью оказались процентами по этому долгу.

Готово, ветка `151-dds-order-model`. ## Что сделано - `sql/ddl/45-dds-tables.sql` — `dds.order_rep` / `_dist` и двойник `dds.order_stage_rep` / `_dist`, донор партиций; `dds.order_v` добавлено в `50-dds-views.sql`. - `sql/dds/` — три запроса: объём прогона, сборка в двойник, замена партиции. - `dags/dds_transform.py` — группа задач `order`: объём, сборка, замена по дню размноженной задачей. - Окно K пришло на сторону стенда якорем в `compose.yaml`. ## Решения по развилкам тикета **Вывод дышащих дней.** Отбор по `snapshot_date` строк отвергнут: у неизменившегося заказа ярлык выжившей версии — жребий. Замер на стартовом мире: последний слепок нёс все 1662 заказа, ярлык последнего слепка сохранили 1188. Окно считается от позднейшего дня заказа в ODS плюс K; вывод и замер — в `sql/dds/order_scope.sql` и `etl.md`. **Где живёт K.** Копией в `compose.yaml`, владелец числа — `generator/world.py`. Опись мира как единый источник отвергнута: хранилище не должно читать артефакт сборки источника. Риск дрейфа назван и передан контуру DQ. **Двойник.** Рядом с целью в `dds`, отдельная база не заводилась: `storage.md` уже держит позицию «право доступа шире договора». ## Ревью Слепое ревью Codex (свежий тред, 100 команд, все шесть критериев перепроверены независимо): шесть находок, `CHANGES_REQUIRED`. Исправлено четыре, две приняты с доводом. Главная — прогресс слоя считался по `max(order_date)`, и упавшая замена оставляла дыру, которую следующий прогон не видел. Проверено на живых данных: партиция `2026-06-01` снесена руками, старая формула отдала `06-02 … 06-08` (дыра навсегда), новая — `06-01 … 06-08`; после прогона день восстановился побайтово, отпечаток `16124F97EAE696C9` совпал, `_load_id` новый. Остаток той же находки — половинчатая замена по шардам, когда `ON CLUSTER` прошёл на одном шарде из двух, — сюда не чинится: день в слое присутствует, и отличить его может только сверка с источником. Вместе с дрейфом K, сдвигом края окна на пустом дне и молчаливыми нулями в `items` это записано в `etl.md`, раздел «Чего конвейер о себе не знает», и адресовано контуру DQ. ## Хвост Заведён #163: координаты доставки в ODS — ярлык, которому нельзя верить. Три из шести находок ревью оказались процентами по этому долгу.
Author
Owner

Влито в main: PR #164 (коммиты 1e8b83f, 535b037). Все критерии приёмки отмечены. Хвост — #163.

Влито в main: PR #164 (коммиты 1e8b83f, 535b037). Все критерии приёмки отмечены. Хвост — #163.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: ddmitry/clickstream-data-platform#151