docs(adr): версия заказа в ODS пересмотрена на дату слепка

- Зачем:
  - лотерея ярлыков доставки при ничьей по updated_at делала слой
    недетерминированным и держала окно K двумя копиями без сверки (#163).
- Что:
  - добавлен ADR 0014: версия ods.order — snapshot_date, с ответом доводу
    ADR 0010, границами допущения и веером отклонённых вариантов.
  - в ADR 0010 помечен пересмотр колонки версии.
- Проверка:
  - реализация и замеры — тикет #165; решение кодом пока не применено.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
ddadminandClaude Fable 5 committed 2026-08-26 06:38:46 -04:00
1 parent 9ab43e79cc
commit c361c36e3d
2 files changed
+99 -1

No files matched your search

+3 -1
View File
@@ -1,7 +1,9 @@
# ADR 0010. Заказы в ODS: версии сущности вместо подмены слепка # ADR 0010. Заказы в ODS: версии сущности вместо подмены слепка
Дата: 16 августа 2026 года. Статус: принято. Частично заменяет Дата: 16 августа 2026 года. Статус: принято. Частично заменяет
[ADR 0008](0008-order-ingestion.md). [ADR 0008](0008-order-ingestion.md). Колонка версии пересмотрена
[ADR 0014](0014-order-version-snapshot-date.md): вместо `updated_at` —
`snapshot_date`.
## Решение ## Решение
@@ -0,0 +1,96 @@
# ADR 0014. Версия заказа в ODS: дата слепка
Дата: 26 августа 2026 года. Статус: принято. Пересматривает колонку версии
из [ADR 0010](0010-order-versions-in-ods.md); остальные решения того ADR в
силе. Развилка разобрана в #163, реализация — #165.
## Решение
`ods.order` хранит версии заказа в `ReplacingMergeTree(snapshot_date)`: при
схлопывании выживает строка позднейшего слепка, который нёс заказ. Формула
решения: у полного слепка версия — сам слепок; истина — то, что источник
сказал последним.
Ключ сущности, партиция и шардирование не меняются. `updated_at` остаётся
деловым полем, и деловой ответ слоя от смены версии не зависит: на
канонических слепках выбор по дате слепка побайтово совпадает с выбором по
деловой метке, потому что метка монотонна по слепкам, а слепок дня — чистая
функция зерна и дня (тесты генератора
`test_the_state_is_read_at_the_boundary_of_the_day` и
`test_two_runs_give_the_same_snapshot`).
Решение покупает две вещи.
- **Детерминизм слоя.** Разные слепки несут разные даты — ничьей между
содержательно разными строками не бывает. При версии `updated_at` доставки
неизменившегося заказа равны по версии, и выжившая строка — жребий: у 86
заказов стартового мира выживала строка серединного слепка, притом что у
всех 3034 пар строк с равным `updated_at` деловые поля совпадали побайтово
(замеры #137). Документация ClickHouse обещает при ничьей победу последней
вставленной строки — замер это опроверг, полагаться на неё было бы ошибкой.
- **Правдивый ярлык.** `snapshot_date` выжившей строки — последний слепок,
который нёс заказ. Объём пересборки DDS читается прямо из данных, причём
без `FINAL`: слияние сохраняет максимум даты по построению. Константа
окна K уходит со стороны стенда вместе с риском дрейфа её двух копий
(ревью #151 ставило этому риску MAJOR).
## Ответ доводу ADR 0010
ADR 0010 отверг семейство «публикацию решает дата наблюдения»: у потребителя
нет признака полноты слепка. Довод целил в подмену партиции — ей полнота
нужна, потому что неполная партиция стёрла бы уже принятые версии. Колонке
версии полнота не нужна: она сравнивает свежесть строк одного заказа, и
недоехавшая строка просто оставляет в силе предыдущую — ровно как при версии
по деловой метке.
## Границы допущения
- **Дисциплина слепков.** Слепок с поздней датой не несёт состояния старше.
Источник, собирающий слепок из отставшей реплики, это ломает; модельный
мир — нет: см. тесты выше.
- **Одна дата — один слепок.** Ограничение вшито в движок: вторая
внутридневная выгрузка дала бы ничью уже на деловых полях. Это условие
контракта источника, его место — в границе строгого приёма
([приём заказов](../architecture/orders/ingestion.md)).
- **Окно наследует потерю строк.** Частичный отказ вставки после коммита
офсетов может унести старший день последнего слепка — выведенное окно
молча сузится. Лекарство прежнее, сверка слоя с источником в контуре DQ
([конвейер](../architecture/etl.md), «Чего конвейер о себе не знает»).
- **Тихая правка лечится только внутри окна.** Строку, изменённую без
сдвига `updated_at`, перекроет следующий слепок; замёрзший заказ источник
больше не показывает, там лечит лишь сверка полей.
- **Повторная доставка слепка.** Байты совпадают, жребий остаётся только на
`_load_id` и `_load_ts`; ярлык даты детерминирован.
- **Правка живого мира не подхватится.** DDL исполняется при рождении мира,
`ALTER` колонку версии не меняет: решение вступает в силу пересозданием
хранилища (`make rebuild-storage`).
## Отклонённые варианты
- **Оставить как есть, сверку копий K отдать DQ.** Лотерея ярлыков остаётся
задокументированным грехом, окно — константой с риском дрейфа.
- **Убрать координаты доставки из ODS.** Наказывает поле за вопрос, на
который оно не бралось отвечать: расследованию (связка годных строк,
ошибок и сырья) координаты служили честно.
- **Дедупликация на вставке.** Детерминизм «обычно», не по построению:
анти-джойн с собственной целью не атомарен, лаг реплик протаскивает дубли.
Приём теряет урок «разобрал и вставил», а ярлык со смыслом «первый
носитель» хуже для обеих ролей поля: окну нужен последний носитель, и
сырьё живо скорее у него.
- **Склейка `(updated_at, snapshot_date)` в одну колонку версии.** Движок
берёт одну колонку, склейка требует битовой упаковки. Робастнее к
недисциплинированному источнику, но платит читаемостью DDL за защиту от
сценариев, которых модельный мир не производит.
- **Склейка `(snapshot_date, updated_at)`.** Путь отступления: если у
источника появятся переиздания слепка одной даты, версия дорастает до
этой формы. От правок без сдвига метки внутри одной даты не спасает и она.
## Связанные решения
- [ADR 0010](0010-order-versions-in-ods.md) — версии заказа в ODS; здесь
пересмотрена только колонка версии.
- [Приём заказов](../architecture/orders/ingestion.md) — граница строгого
приёма и четыре координаты строки.
- [Модель заказа в DDS](../architecture/dds/order.md) — читатель окна
пересборки.
- Развилка, веер и два холодных ревью — #163.