Зачем: развилка #134 карты #127 — чем и когда гоняются трансформации DDS и витрины-таблицы, пока спека числила etl_pipeline за этапом 5. Что: дока docs/architecture/etl.md — даг etl_pipeline рождается этапом 4: дёргает dds_transform и dm_transform последовательно (DQ — будущее звено); живой день зовёт его с ожиданием после установки позиции, после разгона пачкой менти запускает рукой, world_initialize — последним шагом; параметров нет, объём выводится из world_position и состояния слоёв; первый шаг — ожидание хвоста hits; механика REPLACE PARTITION — этап 4, этапу 5 — партиционная переобработка. Указатели в доках заказа, сессии, README слоя и ретро-пометки в ADR 0008/0009 переведены на новую доку. Проверка: вычитка; резолюция и доводы — в тикете #134. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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 прибытия — в
модель не переезжают. Версию заказа они не задают, а физически выжившие их
значения — жребий: у строк с равным updated_at деловые поля совпадают
побайтово, и какая переживёт схлопывание — произвол (замер —
приём заказов, «Что проверено»). Перенесённый
ярлык выглядел бы фактом, оставаясь жребием; лживая координата хуже
отсутствующей. Отсюда правило слоя: служебные колонки — координаты
собственной загрузки слоя; чужие координаты спрашиваются у их слоя по ключу
сущности. Этим закрыто наследство #85: читатель у чужих ярлыков выше ODS
не нашёлся и при названных читателях модели.
Происшествие качества данных расследуется цепочкой, где каждый шаг
спрашивает свой слой на его языке: по _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(updated_at)ещё раз. Повторил бы механику ODS вторым разом: физически хранил бы устаревшие состояния, вернул быFINALи обёртку-представление — а урок версий менти уже отыграл.- Отдельная сущность
dds.order_item. Вторая таблица, второй вопрос шардирования иJOINтам, где хватаетARRAY JOIN; нормализация из реляционного мира, а не модель под колоночное хранилище.JOINменти увидит в сверке, работу с массивами — только здесь. - Зерно «версия заказа» (история). Читателя нет; ODS историю не обещает, а мир при нужде переигрывается генератором.
- Перенос ярлыков ODS — см. «Служебные колонки».
- Без модели вовсе: витрины читают
ods.order_vнапрямую. Разбор JSON и пересчёт времён расползлись бы по витринам, а модель данных — то, ради чего сущность и строится, — не была бы показана.
Связанные решения
- ADR 0010 — версии заказов в
ODS, устойчивая поверхность
ods.order_v. - Приём заказов — граница строгого приёма и четыре координаты строки ODS.
- Проталкивание условий сквозь
представления —
резолюция #128 той же карты: цена
FINALи цепочек представлений, фон, на котором здесь выбрана физическая таблица.