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:
1 parent
9ab43e79cc
commit
c361c36e3d
2 files changed
+99
-1
No files matched your search
@@ -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.
|
||||||
Reference in new issue
Block a user