# 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.