Координаты доставки в ODS: ярлык, которому нельзя верить #163

Closed
opened 2026-08-26 12:17:45 +03:00 by ddmitry · 2 comments
Owner

Цель

Решить судьбу координат доставки в ods.order — snapshot_date, _load_id,
_load_ts. Сегодня они обещают больше, чем слой может держать: ods.order_rep
схлопывает доставки движком ReplacingMergeTree(updated_at), а у заказа,
который не менялся, все доставки равны по версии. Какая строка переживёт
слияние — произвол, и ярлык выжившей не обязан быть последним. Приём это
признаёт прямым текстом (ingestion.md, «Роль ODS»), то есть слой хранит поле
и тут же просит ему не верить.

Тот же довод репозиторий уже применил этажом выше: в dds/order.md ярлыки ODS
не переезжают в модель именно потому, что «перенесённый ярлык выглядел бы
фактом, оставаясь жребием; лживая координата хуже отсутствующей». Этот тикет —
о том, распространяется ли правило на сам ODS.

Откуда взялось

Долг проявился при исполнении #151 (модель заказа в DDS). Трансформации нужен
был ответ на вопрос «какие дни принёс последний слепок» — и оказалось, что ODS
на него ответить не может. Плата вышла такая:

  • окно пересборки считается не по слепку, а по позднейшему дню заказа, и
    требует константы K, которая живёт двумя копиями — в generator/world.py
    и в compose.yaml, без всякой сверки между ними;
  • край окна съезжает на день назад, если в позднейший день источника не
    родилось ни одного заказа;
  • ревью #151 отдельно отметило дрейф K как MAJOR-риск.

Замер на стартовом мире 26 августа 2026 года: последний слепок нёс все 1662
заказа, а его ярлык сохранили 1188 строк — четверть заказов отбор по
snapshot_date потерял бы молча.

Если бы ярлык был правдивым, окно бралось бы прямо из данных, а константа K
на стороне стенда исчезла бы вместе с риском дрейфа.

Что решить

Три исхода, все допустимы:

  1. Убрать координаты доставки из ODS. Вопрос «что приехало» задаётся тому,
    кто может ответить: сырью STG, пока живо, или журналу загрузок, если он
    появится. Лживого поля просто нет. Цена — расследование происшествия теряет
    шаг, а журнал загрузок это отдельная постройка.
  2. Сделать ярлык правдивым. Доломать ничью по updated_at датой слепка,
    чтобы выживала строка последней доставки. Деловой ответ не меняется — у
    равных версий поля совпадают, — а ярлык начинает значить «последний раз
    виден тогда-то». Цена — корректность привязывается к монотонности слепков:
    доставка не по порядку выберет версию по ярлыку, а не по делу.
  3. Оставить как есть, записав довод. Тогда K остаётся копией, а сверку
    копий берёт на себя контур DQ.

Развилка существенная, поэтому вести её через brainstorm-with-docs: сначала
веер, потом конвергенция, отклонённые варианты — в запись.

Границы

  • Тикет вправе ограничиться этой логикой, но чинить её сквозным вариантом:
    DDL ODS, шаг разбора, дока приёма, ADR и потребители — всё одним PR, чтобы
    контракт нигде не разошёлся сам с собой.
  • Вправе и отклонить: обоснованный отказ с записью — законный исход.
  • Окно пересборки в DDS этим тикетом не переписывается. Если решение упростит
    sql/dds/order_scope.sql и снимет K со стенда, это делается тем же PR — но
    как следствие, а не как самостоятельная цель.
  • Журнал загрузок как отдельная сущность — не здесь.

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

  • docs/architecture/orders/ingestion.md — «Роль ODS», четыре координаты
    строки и признание про жребий;
  • docs/adr/0010-order-versions-in-ods.md — почему версия выбирается по
    updated_at;
  • docs/architecture/dds/order.md — «Служебные колонки», правило про лживую
    координату;
  • docs/architecture/etl.md — «Объём прогона» и «Чего конвейер о себе не
    знает»;
  • sql/dds/order_scope.sql — что трансформация делает вместо честного ответа.

Критерии приёмки

  • Решение записано там, где живёт контракт: ADR 0010 либо
    orders/ingestion.md, и оба текста не противоречат друг другу.
  • Если координаты меняются — DDL, разбор, дока и потребители правятся
    одним PR; мир пересобирается, приём заказов остаётся зелёным.
  • Если ярлык становится правдивым — показано замером, что после нескольких
    слепков snapshot_date выжившей строки равен последнему слепку, который
    её нёс.
  • Если решено оставить — довод записан рядом с полем, и сверка копий K
    названа задачей контура DQ.
  • make lint; на стенде make smoke, make check-clickhouse,
    make check-services зелёные.
## Цель Решить судьбу координат доставки в `ods.order` — `snapshot_date`, `_load_id`, `_load_ts`. Сегодня они обещают больше, чем слой может держать: `ods.order_rep` схлопывает доставки движком `ReplacingMergeTree(updated_at)`, а у заказа, который не менялся, все доставки равны по версии. Какая строка переживёт слияние — произвол, и ярлык выжившей не обязан быть последним. Приём это признаёт прямым текстом (`ingestion.md`, «Роль ODS»), то есть слой хранит поле и тут же просит ему не верить. Тот же довод репозиторий уже применил этажом выше: в `dds/order.md` ярлыки ODS не переезжают в модель именно потому, что «перенесённый ярлык выглядел бы фактом, оставаясь жребием; лживая координата хуже отсутствующей». Этот тикет — о том, распространяется ли правило на сам ODS. ## Откуда взялось Долг проявился при исполнении #151 (модель заказа в DDS). Трансформации нужен был ответ на вопрос «какие дни принёс последний слепок» — и оказалось, что ODS на него ответить не может. Плата вышла такая: - окно пересборки считается не по слепку, а по позднейшему дню заказа, и требует константы K, которая живёт **двумя копиями** — в `generator/world.py` и в `compose.yaml`, без всякой сверки между ними; - край окна съезжает на день назад, если в позднейший день источника не родилось ни одного заказа; - ревью #151 отдельно отметило дрейф K как MAJOR-риск. Замер на стартовом мире 26 августа 2026 года: последний слепок нёс все 1662 заказа, а его ярлык сохранили 1188 строк — четверть заказов отбор по `snapshot_date` потерял бы молча. **Если бы ярлык был правдивым, окно бралось бы прямо из данных, а константа K на стороне стенда исчезла бы вместе с риском дрейфа.** ## Что решить Три исхода, все допустимы: 1. **Убрать координаты доставки из ODS.** Вопрос «что приехало» задаётся тому, кто может ответить: сырью STG, пока живо, или журналу загрузок, если он появится. Лживого поля просто нет. Цена — расследование происшествия теряет шаг, а журнал загрузок это отдельная постройка. 2. **Сделать ярлык правдивым.** Доломать ничью по `updated_at` датой слепка, чтобы выживала строка последней доставки. Деловой ответ не меняется — у равных версий поля совпадают, — а ярлык начинает значить «последний раз виден тогда-то». Цена — корректность привязывается к монотонности слепков: доставка не по порядку выберет версию по ярлыку, а не по делу. 3. **Оставить как есть**, записав довод. Тогда K остаётся копией, а сверку копий берёт на себя контур DQ. Развилка существенная, поэтому вести её через `brainstorm-with-docs`: сначала веер, потом конвергенция, отклонённые варианты — в запись. ## Границы - Тикет вправе ограничиться **этой** логикой, но чинить её сквозным вариантом: DDL ODS, шаг разбора, дока приёма, ADR и потребители — всё одним PR, чтобы контракт нигде не разошёлся сам с собой. - Вправе и отклонить: обоснованный отказ с записью — законный исход. - Окно пересборки в DDS этим тикетом не переписывается. Если решение упростит `sql/dds/order_scope.sql` и снимет K со стенда, это делается тем же PR — но как следствие, а не как самостоятельная цель. - Журнал загрузок как отдельная сущность — не здесь. ## Сначала прочитать - `docs/architecture/orders/ingestion.md` — «Роль ODS», четыре координаты строки и признание про жребий; - `docs/adr/0010-order-versions-in-ods.md` — почему версия выбирается по `updated_at`; - `docs/architecture/dds/order.md` — «Служебные колонки», правило про лживую координату; - `docs/architecture/etl.md` — «Объём прогона» и «Чего конвейер о себе не знает»; - `sql/dds/order_scope.sql` — что трансформация делает вместо честного ответа. ## Критерии приёмки - [x] Решение записано там, где живёт контракт: ADR 0010 либо `orders/ingestion.md`, и оба текста не противоречат друг другу. - [ ] Если координаты меняются — DDL, разбор, дока и потребители правятся одним PR; мир пересобирается, приём заказов остаётся зелёным. - [ ] Если ярлык становится правдивым — показано замером, что после нескольких слепков `snapshot_date` выжившей строки равен последнему слепку, который её нёс. - [ ] Если решено оставить — довод записан рядом с полем, и сверка копий K названа задачей контура DQ. - [ ] `make lint`; на стенде `make smoke`, `make check-clickhouse`, `make check-services` зелёные.
ddmitry added the needs-triagewayfinder:grilling labels 2026-08-26 12:17:54 +03:00
Author
Owner

Родился при исполнении #151: там же лежат замеры (1662 против 1188) и вывод объёма, который обходится без правдивого ярлыка.

Родился при исполнении #151: там же лежат замеры (1662 против 1188) и вывод объёма, который обходится без правдивого ярлыка.
Author
Owner

Резолюция развилки. Выбран второй исход, но в форме, которой веер постановки не содержал: ярлык становится правдивым не добивкой ничьей, а сменой самой колонки версии — ReplacingMergeTree(snapshot_date). Формула решения: у полного слепка версия — сам слепок; истина — то, что источник сказал последним.

Решение записано в ADR 0014 (коммит c361c36) — там ответ доводу ADR 0010, границы допущения и веер отклонённых: обе склейки версий, дедупликация на вставке, удаление поля, статус-кво. ADR 0010 помечен пересмотренным в части колонки версии.

Перед записью решение прошло два холодных ревью свежими сессиями без контекста обсуждения.

  • Развёртка зависимостей: полный список мест, где updated_at назван версией, — перенесён в объём #165. Попутно выяснилось, что «одна дата — один слепок» и монотонность метки — не допущения, а тестированные инварианты генератора.
  • Адверсарное: вердикт «выстояло», сломать деловое содержимое внутри модели не удалось. Главная находка: смена движка не подхватывается живым миром (DDL исполняется при рождении, CREATE IF NOT EXISTS) — решение вступает в силу только пересозданием хранилища. Владелец решил обойтись без сторожа в скриптах: мир пересобирается один раз.

Замер 1662/1188 из постановки объяснился до конца: документация ClickHouse обещает при ничьей победу последней вставленной строки, а стенд показал обратное (86 заказов с ярлыком серединного слепка) — на слово докам здесь верить нельзя, и это зафиксировано в ADR.

Реализация — #165: DDL с пересборкой мира, снятие K со стенда, упрощение order_scope.sql, правка документов одним PR. Первый критерий приёмки закрыт ADR; остальные — условные ветки исполнения и переезжают в #165 вместе с замером правдивости ярлыка.

Резолюция развилки. Выбран второй исход, но в форме, которой веер постановки не содержал: ярлык становится правдивым не добивкой ничьей, а сменой самой колонки версии — `ReplacingMergeTree(snapshot_date)`. Формула решения: у полного слепка версия — сам слепок; истина — то, что источник сказал последним. Решение записано в [ADR 0014](https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/adr/0014-order-version-snapshot-date.md) (коммит c361c36) — там ответ доводу ADR 0010, границы допущения и веер отклонённых: обе склейки версий, дедупликация на вставке, удаление поля, статус-кво. ADR 0010 помечен пересмотренным в части колонки версии. Перед записью решение прошло два холодных ревью свежими сессиями без контекста обсуждения. - Развёртка зависимостей: полный список мест, где `updated_at` назван версией, — перенесён в объём #165. Попутно выяснилось, что «одна дата — один слепок» и монотонность метки — не допущения, а тестированные инварианты генератора. - Адверсарное: вердикт «выстояло», сломать деловое содержимое внутри модели не удалось. Главная находка: смена движка не подхватывается живым миром (DDL исполняется при рождении, `CREATE IF NOT EXISTS`) — решение вступает в силу только пересозданием хранилища. Владелец решил обойтись без сторожа в скриптах: мир пересобирается один раз. Замер 1662/1188 из постановки объяснился до конца: документация ClickHouse обещает при ничьей победу последней вставленной строки, а стенд показал обратное (86 заказов с ярлыком серединного слепка) — на слово докам здесь верить нельзя, и это зафиксировано в ADR. Реализация — #165: DDL с пересборкой мира, снятие K со стенда, упрощение `order_scope.sql`, правка документов одним PR. Первый критерий приёмки закрыт ADR; остальные — условные ветки исполнения и переезжают в #165 вместе с замером правдивости ярлыка.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ddmitry/clickstream-data-platform#163