Files
clickstream-data-platform/docs/adr/0014-order-version-snapshot-date.md
ddadminandClaude Fable 5 c361c36e3d 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>
2026-08-26 06:38:46 -04:00

8.3 KiB

ADR 0014. Версия заказа в ODS: дата слепка

Дата: 26 августа 2026 года. Статус: принято. Пересматривает колонку версии из ADR 0010; остальные решения того 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 отверг семейство «публикацию решает дата наблюдения»: у потребителя нет признака полноты слепка. Довод целил в подмену партиции — ей полнота нужна, потому что неполная партиция стёрла бы уже принятые версии. Колонке версии полнота не нужна: она сравнивает свежесть строк одного заказа, и недоехавшая строка просто оставляет в силе предыдущую — ровно как при версии по деловой метке.

Границы допущения

  • Дисциплина слепков. Слепок с поздней датой не несёт состояния старше. Источник, собирающий слепок из отставшей реплики, это ломает; модельный мир — нет: см. тесты выше.
  • Одна дата — один слепок. Ограничение вшито в движок: вторая внутридневная выгрузка дала бы ничью уже на деловых полях. Это условие контракта источника, его место — в границе строгого приёма (приём заказов).
  • Окно наследует потерю строк. Частичный отказ вставки после коммита офсетов может унести старший день последнего слепка — выведенное окно молча сузится. Лекарство прежнее, сверка слоя с источником в контуре DQ (конвейер, «Чего конвейер о себе не знает»).
  • Тихая правка лечится только внутри окна. Строку, изменённую без сдвига 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 — версии заказа в ODS; здесь пересмотрена только колонка версии.
  • Приём заказов — граница строгого приёма и четыре координаты строки.
  • Модель заказа в DDS — читатель окна пересборки.
  • Развилка, веер и два холодных ревью — #163.