Files
clickstream-data-platform/docs/architecture/dds/order.md
T
ddadmin a9cead2974 docs(orders): документация перестроена вокруг текущей модели
- Зачем:
  - описание вело нового читателя через историю исправления вместо действующего
    контракта слоёв.
- Что:
  - прямо описаны версия заказа, границы пакетного приёма и расчёт окна DDS.
  - ответственность ODS, DDS и конвейера разделена без ссылок на прежние
    ошибки как на основной довод.
  - исторический вывод исследования помечен до устаревшего абзаца, а ссылки
    ведут к отдельным решениям и контрактам.
- Проверка:
  - make lint.
  - make config-test.
2026-08-26 18:47:22 +03:00

11 KiB

Модель заказа в DDS

Учебный результат: менти проектирует бизнес-сущность поверх готового ODS — выбирает зерно, состав строки и способ наполнения — и видит идемпотентность трансформации, устроенную иначе, чем в приёме: заменой партиции, а не версиями.

Зерно

Одна строка — заказ в текущем состоянии, ключ — order_id. Модель отвечает на вопрос «что сейчас известно о заказе»; историю версий не хранит: читателя у истории нет ни в одной витрине, а ODS её и не обещает — промежуточные версии съедают фоновые слияния. «Дыхание» выручки внутри окна K живёт на текущем состоянии само: пересборка дня меняет статусы заказов, и число дня меняется вместе с ними.

Состав строки

Деловые поля источника переезжают из ods.order_v без переименований — бэкенд уже говорит в snake_case: order_id, user_id, status, деньги в Decimal (items_total, discount, delivery, total).

Времена created_at и updated_at пересчитываются при загрузке в пояс счётчика: DDS обслуживает человека с дашбордом (дока хранилища, «Часовые пояса»). Там же один раз считается день заказа — колонка order_date, по образцу EventDate у события: суточные срезы группируются по готовой дате и пояса не упоминают.

Позиции разобраны из сырой строки items в три типизированных массива одной длины — артикул, количество, цена (Decimal). Это родная для ClickHouse денормализация: позиция не живёт без заказа и собственного ключа не имеет, а разворачивает массивы читатель — ARRAY JOIN в витрине выручки, с обогащением категорией через dictGet (мастер-спека, раздел 7). Разбор JSON происходит один раз, на границе ODS → DDS, куда его и положила спека (раздел 2); записывать его параллельными колонками-массивами или Nested — выбор DDL, не модели.

Движок и наполнение

dds.order_rep — ReplicatedMergeTree без колонки версии: партиция — order_date (день заказа неизменен), ключ сортировки — order_id. dds.order_dist шардирует по cityHash64(order_id), тем же выражением, что ODS: это единственный устойчивый ключ сущности.

Идемпотентность держит замена партиции, а не версии. Сбор дня — батч после прихода слепка: трансформация собирает партицию заново из ods.order_v и атомарно подменяет. Поэтому каждая строка физической таблицы — ровно один заказ, а чтению не нужен FINAL. Пока окно K живо, день «дышит» — статусы заказов меняются, и партиция дня пересобирается очередным запуском; на выходе из окна день замерзает.

Публичная поверхность — dds.order_v: обычное представление с явным списком деловых и собственных служебных колонок над dds.order_dist, без FINAL. Роли объектов и доступ следуют общей конвенции, «Имена объектов».

Механика запуска — где собирается партиция и как подменяется на кластере — сюда не входит: это устройство дневного конвейера.

Служебные колонки

Строка получает _load_id и _load_ts запуска трансформации, собравшего её партицию, — по общей конвенции хранилища. При замене партиции они одинаковы у всех строк дня и отвечают на вопрос «каким запуском и когда собран этот день».

Ярлыки ODS (snapshot_date, _load_id приёма, _load_ts прибытия) в модель не переезжают. Они описывают наблюдение и доставку строки в ODS. Модель DDS хранит текущее деловое состояние заказа. В ODS snapshot_date задаёт версию и называет последний слепок, в котором источник показал заказ (ADR 0014). Дату наблюдения и координаты доставки читатель спрашивает у ODS по order_id. Правило слоя: служебные колонки — координаты собственной загрузки слоя; чужие координаты спрашиваются у их слоя по ключу сущности.

Происшествие качества данных расследуется цепочкой, где каждый шаг спрашивает свой слой на его языке: по _load_id строки DDS видно, какой запуск собрал день; по order_id ODS показывает физические версии и брак — там свой _load_id приёма связывает годные строки, ошибки и сырьё; дальше сырьё STG, пока живо. Окно расследования задано сроками хранения: сырьё — трое суток, брак — месяц (дока хранилища).

Читатели

  • Витрина выручки dm.revenue_daily_v: заказы в paid, ARRAY JOIN позиций, категория через dictGet (спека, раздел 7).
  • Сверка dm.purchase_vs_orders_v: order_id, order_date, items_total, status; соединение — GLOBAL JOIN с обеими сторонами-подзапросами (исследование соединений).
  • Сборка карты идентичностей: пара order_id ↔ user_id, источник — dds.order_v.

Не входит

  • Механика запуска трансформации и замены партиций на кластере — дневной конвейер.
  • Контур проверок качества (даг DQ, dm.dq_summary) — отдельный разговор этапа 4.
  • Инкрементальное наполнение вместо пересборки — техдолг #2.

Отклонённые варианты

  • Одно динамическое представление вместо физической таблицы. Рамка стенда — показать модель данных, которую хранилище готовит потребителям; урок «слой — контракт, а не копия» уже закреплён за dds.event_v, а разбор позиций и пересчёт времён при каждом чтении собирали бы модель заново на лету. Публичное dds.order_v поверх готовой физической модели этому доводу не противоречит: оно ничего заново не вычисляет.
  • ReplacingMergeTree ещё раз. Повторил бы механику ODS вторым разом: физически хранил бы устаревшие состояния, вернул бы FINAL и обёртку-представление — а урок версий менти уже отыграл.
  • Отдельная сущность dds.order_item. Вторая таблица, второй вопрос шардирования и JOIN там, где хватает ARRAY JOIN; нормализация из реляционного мира, а не модель под колоночное хранилище. JOIN менти увидит в сверке, работу с массивами — только здесь.
  • Зерно «версия заказа» (история). Читателя нет; ODS историю не обещает, а мир при нужде переигрывается генератором.
  • Перенос ярлыков ODS — см. «Служебные колонки».
  • Без модели вовсе: витрины читают ods.order_v напрямую. Разбор JSON и пересчёт времён расползлись бы по витринам, а модель данных — то, ради чего сущность и строится, — не была бы показана.

Связанные решения