Зачем: помочь начинающему инженеру проследить событие и версию заказа от генератора до хранилища, включая ошибки и повторные поступления. Что: обновлены вводная генератора, справочники приема и модели заказа, разделы хранилища и комментарии SQL и дага. Сохранены технические условия; исполняемый код, схемы и поведение стенда не изменены. Проверка: make lint, git diff --check, 142 локальные ссылки и справка CLI. SQL без комментариев и Python AST без строк документации совпали с исходными. Генерация данных и сброс состояния не выполнялись. Refs #250
114 lines
8.2 KiB
Markdown
114 lines
8.2 KiB
Markdown
# Модель заказа в DDS
|
||
|
||
DDS готовит заказ для анализа: выбирает текущее состояние из ODS,
|
||
разбирает позиции и переводит время в часовой пояс счетчика. Этот слой
|
||
показывает, как собрать модель данных для витрин и повторить загрузку
|
||
без удвоения заказов.
|
||
|
||
## Зерно
|
||
|
||
Одна строка — один заказ в последнем известном состоянии, ключ — `order_id`.
|
||
История изменений здесь не хранится. ODS тоже не служит ее архивом:
|
||
промежуточные версии удаляются при фоновых слияниях.
|
||
|
||
## Состав строки
|
||
|
||
Поля заказа приходят из `ods.order_v` с именами источника: `order_id`,
|
||
`user_id`, `status`, `items_total`, `discount`, `delivery`, `total`.
|
||
Денежные колонки хранят рубли с копейками в `Decimal(18, 2)`.
|
||
|
||
`created_at` и `updated_at` переводятся из UTC в `Europe/Samara`, пояс
|
||
счетчика сайта. Из `created_at` в этом поясе вычисляется `order_date` —
|
||
день оформления заказа. Витрины группируют по готовой дате;
|
||
правило времени общее для [хранилища](../storage.md#часовые-пояса).
|
||
|
||
Строка `items` из ODS разбирается в три массива одной длины:
|
||
|
||
| Колонка | Что хранит |
|
||
|---|---|
|
||
| `item_sku` | Артикулы товаров |
|
||
| `item_quantity` | Количество каждого товара, `UInt32` |
|
||
| `item_price` | Цены за единицу в рублях, `Decimal(18, 2)` |
|
||
|
||
Элементы с одинаковым номером описывают одну позицию заказа. Витрина
|
||
выручки разворачивает их через `ARRAY JOIN` и получает категорию товара
|
||
из справочника через `dictGet`. Разбор JSON выполняется один раз
|
||
при загрузке DDS.
|
||
|
||
ODS проверяет только то, что `items` — массив. Разбор в DDS не заменяет
|
||
проверку его содержимого: например, некорректное количество может стать
|
||
нулем. Проверку суммы позиций участник добавляет в
|
||
[первом задании ступени 5](../../labs/5-1-order-checks.md).
|
||
|
||
## Движок и наполнение
|
||
|
||
`dds.order_rep` — таблица на `ReplicatedMergeTree`, без колонки версии.
|
||
Партиция хранит один `order_date`, ключ сортировки — `order_id`.
|
||
Запись через `dds.order_dist` распределяет заказы по `cityHash64(order_id)`,
|
||
как в ODS. Для чтения служит `dds.order_v` с явным списком колонок
|
||
над `dds.order_dist`; `FINAL` в этом слое не нужен.
|
||
|
||
Даг `dds_transform` пересобирает выбранные дни из `ods.order_v`:
|
||
|
||
1. [`order_scope.sql`](../../../sql/dds/order_scope.sql) определяет
|
||
непрерывный отрезок `first_day`…`last_day`. Он включает дни заказов
|
||
из последнего слепка и начинается раньше, если в DDS отсутствует
|
||
день, который есть в ODS.
|
||
2. [`order_rebuild.sql`](../../../sql/dds/order_rebuild.sql) очищает
|
||
промежуточную таблицу `dds.order_stage_rep` и наполняет ее через
|
||
`dds.order_stage_dist`. Это донор: в нем готовится результат,
|
||
пока основная таблица остается доступной для чтения.
|
||
3. [`order_replace.sql`](../../../sql/dds/order_replace.sql) заменяет
|
||
каждую дневную партицию основной таблицы готовой партицией донора.
|
||
|
||
Повтор при неизменном ODS дает те же деловые данные без дублей —
|
||
это идемпотентность загрузки. Пока заказы дня входят в семидневное окно
|
||
источника, их состояние может меняться, и день пересобирается.
|
||
За пределами окна новые состояния не приходят, но пропущенный день
|
||
можно восстановить из ODS. Границы пересборки берутся из данных;
|
||
число семь в SQL хранилища не повторяется.
|
||
|
||
Замена атомарна внутри одного шарда: читатель видит прежнюю или новую
|
||
партицию целиком. Замены на двух шардах независимы, поэтому между ними
|
||
запрос через Distributed может увидеть смешанный результат.
|
||
Донор остается до следующей сборки и доступен для диагностики.
|
||
Порядок задач и повторы — в [описании дневного конвейера](../etl.md).
|
||
|
||
## Служебные колонки
|
||
|
||
`_load_id` и `_load_ts` показывают, какой запуск DDS собрал строку
|
||
и когда он выполнил вставку. Они одинаковы у строк одной сборки.
|
||
При следующей пересборке эти метки обновятся, даже если заказ не изменился.
|
||
|
||
`snapshot_date` и метки приема ODS в DDS не переносятся: они описывают
|
||
получение сообщения источника. Их можно найти в ODS по `order_id`.
|
||
В каждом слое служебные колонки относятся к его собственной загрузке.
|
||
|
||
При поиске ошибки начните с `_load_id` в DDS: он указывает на запуск
|
||
сборки. По `order_id` найдите версии заказа в ODS, а по их `_load_id` —
|
||
соответствующие сырые сообщения в STG и ошибки того же приема.
|
||
В таблице ошибок `order_id` может быть не разобран, поэтому нужное
|
||
сообщение ищут по `raw` и координатам Kafka.
|
||
Сырые сообщения хранятся около трех реальных суток, ошибки — месяц;
|
||
уточнения к срокам — в [справочнике хранилища](../storage.md#срок-жизни-сырых-данных).
|
||
|
||
## Читатели
|
||
|
||
- `dm.revenue_daily_v` считает выручку по заказам со статусом `paid`,
|
||
разворачивает позиции и дополняет их категорией товара.
|
||
- `dm.purchase_vs_orders_v` сопоставляет заказ с покупкой на сайте
|
||
по `order_id`, используя `order_date`, `items_total` и `status`.
|
||
- `dds.identity_map` связывает идентификаторы посетителей с `user_id`
|
||
через покупки и заказы.
|
||
|
||
## Связанные решения
|
||
|
||
- [ADR 0010](../../adr/0010-order-versions-in-ods.md) — версии заказов в ODS
|
||
и чтение через `ods.order_v`.
|
||
- [Прием заказов](../orders/ingestion.md) — проверки формата и выбор
|
||
актуальной версии.
|
||
- [Исследование соединений](../../research/2026-08-22-distributed-join.md) —
|
||
почему сверка распределенных источников использует `GLOBAL JOIN`.
|
||
- [Проталкивание условий сквозь представления](../../research/2026-08-22-view-predicate-pushdown.md) —
|
||
как фильтр запроса влияет на объем чтения ODS.
|