feat(orders): добавить приём слепков в STG
Зачем: - связать проигрывание модельных дней с пакетным приёмом заказов - показать на одном стенде различие потокового push и пакетного pull Что: - добавлен топик, Kafka-чтец и реплицированное сырьё заказов - добавлен даг orders_ingest с одним прямым чтением и идентификатором загрузки - работники мира отправляют слепок, ждут приём и только затем двигают позицию - решения, границы отказа и проверки отражены в ADR и архитектурных документах Проверка: - make lint - make config-test - make smoke - make check-clickhouse - make check-services
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
# ADR 0008. Приём заказов: пакетный забор слепка, инициируемый Airflow
|
||||
|
||||
Дата: 12 августа 2026 года. Статус: частично заменено
|
||||
[ADR 0010](0010-order-versions-in-ods.md). Реализация — отдельным тикетом.
|
||||
[ADR 0010](0010-order-versions-in-ods.md); забор построен тикетом #93.
|
||||
|
||||
Сохраняются пакетный забор, `RawBLOB`, один чтец на `clickhouse-01`, одна
|
||||
партиция топика и отсутствие матвью. Отменены граница «одно чтение — полный
|
||||
слепок», замена партиции `snapshot_date`, ODS без дедупликации и готовая схема
|
||||
`dds.order` с `argMax`. Действующая форма STG → ODS описана в
|
||||
[спецификации приёма заказов](../architecture/orders/ingestion.md).
|
||||
`dds.order` с `argMax` — это ADR 0010. Отдельно от него, решением #93 от
|
||||
17 августа 2026 года, производитель и потребитель слепка разъехались по разным
|
||||
дагам; раздел «Решение» описывает построенное. Действующая форма STG → ODS
|
||||
описана в [спецификации приёма заказов](../architecture/orders/ingestion.md).
|
||||
|
||||
## Решение
|
||||
|
||||
@@ -15,13 +17,18 @@
|
||||
у событий ([ADR 0005](0005-event-ingestion.md)). Матвью к ней не привязана.
|
||||
Сырьё забирает пакетный шаг, которым управляет Airflow: он вставляет
|
||||
прочитанное в `stg.orders_raw_dist`, разбирает его в типизированный слепок и
|
||||
заменяет партицию дня в `ods.order_snapshot`. Тот же даг проигрывает модельный
|
||||
день генератором, поэтому переливается ровно то, что он положил в топик.
|
||||
Уточнение со сдвигом отправки, решённым позже (#71, [слепок и его
|
||||
доставка](../architecture/orders/snapshot.md)): даг, играющий день D, в
|
||||
штатном прогоне кладёт и забирает слепок дня D−1 — слепок предыдущего дня, а
|
||||
не сыгранного. После падения между шагами в топике может ждать и хвост
|
||||
прежних слепков; забор принимает всё приехавшее.
|
||||
заменяет партицию дня в `ods.order_snapshot`.
|
||||
|
||||
Производитель и потребитель слепка — разные даги. Работник пульта мира играет
|
||||
день и отправляет слепок, а забирает его отдельный даг `orders_ingest`; зовёт
|
||||
забор сам работник ждущим `TriggerDagRunOperator` и зеленеет только вслед за
|
||||
ним. Связывает две половины не общий даг, а этот дождавшийся успех: зелёный
|
||||
работник значит, что забор отработал. Уточнение со сдвигом
|
||||
отправки, решённым позже (#71, [слепок и его
|
||||
доставка](../architecture/orders/snapshot.md)): работник, играющий день D, в
|
||||
штатном прогоне кладёт слепок дня D−1 — слепок предыдущего дня, а не
|
||||
сыгранного. После падения между шагами в топике может ждать и хвост прежних
|
||||
слепков; забор принимает всё приехавшее.
|
||||
|
||||
Прямое чтение из Kafka-движка требует двух настроек, и вторая не очевидна:
|
||||
`stream_like_engine_allow_direct_select = 1` разрешает читать чтеца запросом,
|
||||
@@ -47,8 +54,8 @@
|
||||
партиции** `snapshot_date`, ровно как обещает раздел 2 мастер-спеки;
|
||||
- `dds.order` — дедуп до последней версии через `argMax`, без изменений.
|
||||
|
||||
Сенсора дневного батча нет. Ждать нечего: производитель и потребитель слепка
|
||||
живут в одном даге.
|
||||
Сенсора дневного батча нет. Ждать нечего: забор дёргает сам отправитель
|
||||
слепка, когда отправил.
|
||||
|
||||
## Почему
|
||||
|
||||
@@ -112,7 +119,7 @@
|
||||
|
||||
**Что отвергнуто ещё.** Сенсор дневного батча из раздела 9 мастер-спеки: у
|
||||
топика нет сигнала «всё», и сенсор ловил бы момент, которого не существует, —
|
||||
а раз генератор и переливка в одном даге, ждать нечего по построению. Лаба,
|
||||
а раз забор зовёт сам отправитель слепка, ждать нечего по построению. Лаба,
|
||||
поднимающая типизированного чтеца во второй группе потребителей ради того же
|
||||
сравнения: она понадобилась бы, останься сравнение невыполненным, но push
|
||||
против pull даёт его живым и постоянным.
|
||||
@@ -124,10 +131,13 @@
|
||||
один и тот же, и это честная разница двух режимов, а не потеря: при пулле
|
||||
читает тот, кого спросили.
|
||||
|
||||
Гарантий приёма у заказов не больше, чем у событий, но последствия мягче.
|
||||
Офсеты коммитятся при чтении, вставка идёт следом — упавшая вставка теряет
|
||||
пачку. У потока такая потеря невосстановима, у слепка её лечит следующий день:
|
||||
окно изменяемости K = 7 привезёт те же заказы заново.
|
||||
Гарантий приёма у заказов не больше, чем у событий, и граница проходит по
|
||||
живому. Успешное чтение короткой порции оставляет непрочитанный хвост в
|
||||
топике — он дождётся следующего забора. А отказ после чтения теряет саму
|
||||
порцию: офсеты закоммичены в момент чтения, часть строк могла лечь на шарды,
|
||||
и повторное чтение вернёт ноль. Позицию мира это не двигает, поэтому день
|
||||
сыграется и уедет заново; в сырье он тогда окажется частичным дублем, а
|
||||
старый хвост, ушедший той же порцией, не вернётся.
|
||||
|
||||
Этап 3 забирает у этапа 5 первый настоящий даг. Раздел 9 мастер-спеки отдавал
|
||||
даги этапу 5 целиком; приём заказов без дага не существует, поэтому порядок
|
||||
@@ -158,10 +168,34 @@ ClickHouse `26.3.17.56`.
|
||||
- Про `Distributed` поверх Kafka документация не говорит ничего — ни
|
||||
поддержки, ни запрета.
|
||||
|
||||
Осталось проверить при исполнении, и это работа тикета реализации: что второй
|
||||
прогон подряд возвращает пусто, то есть офсеты действительно закоммичены; что
|
||||
одного чтения хватает на весь слепок дня; что виртуальные колонки доставки
|
||||
(`_topic`, `_partition`, `_offset`, `_timestamp_ms`) доступны при прямом чтении
|
||||
— на них стоят служебные колонки сырья, см. [доку
|
||||
хранилища](../architecture/storage.md); что повторная заливка дня даёт в
|
||||
`ods.order_snapshot` тот же счёт, а не удвоенный.
|
||||
Забор проверен на живом стенде 18 августа 2026 года при исполнении #93 —
|
||||
ClickHouse `26.3.17.56`, Airflow 3.3.0. Три вопроса, оставленные этим ADR
|
||||
реализации, закрыты; заодно снят отказной путь. Обе настройки прямого чтения
|
||||
доезжают до запроса, объявленные в конце `INSERT ... SELECT`: `system.query_log`
|
||||
показывает у каждой вставки единицу.
|
||||
|
||||
- **Виртуальные колонки доставки при прямом чтении доступны все четыре.**
|
||||
`_topic`, `_partition`, `_offset` и `_timestamp_ms` читаются тем же
|
||||
выражением, что в матвью приёма событий, и метаданные в `stg.orders_raw`
|
||||
заполнены: 1694 строки слепка дня 7 приехали с `orders`, партицией 0,
|
||||
сплошными офсетами 0…1693 и непустой меткой брокера. Отдельного механизма
|
||||
пуллу не понадобилось.
|
||||
- **Одного чтения хватает и на слепок, и на разгон.** Пять заборов подряд
|
||||
взяли 1694, 1730, 3446, 1694 и 5097 строк — каждый раз ровно столько,
|
||||
сколько напечатал генератор. Числа сверх слепка объясняются сами: 3446 — это
|
||||
свежий слепок плюс хвост, оставшийся от упавшего прогона, а 5097 — три
|
||||
слепка разгона `days = 3`, уехавшие одной порцией.
|
||||
- **Офсеты действительно коммитятся.** Второй забор подряд по пустому топику
|
||||
вернул ноль строк, а офсеты следующего продолжились с 1694 — то есть
|
||||
`kafka_commit_on_select` работает, и слепок не забирается заново.
|
||||
- **Упавший забор не двигает мир.** Чтец снесли, и прогон работника покраснел
|
||||
на триггере забора: `remember_played` ушла в `upstream_failed`, позиция
|
||||
осталась прежней, а после починки тот же день сыгран заново, и ждавший хвост
|
||||
уехал одной порцией вместе со свежим слепком. Чтения в этом опыте не было
|
||||
вовсе — падать было нечему, — поэтому он показывает только несдвиг позиции.
|
||||
Судьба уже прочитанной порции другая, и она описана в «Следствиях».
|
||||
|
||||
Осталось проверить при исполнении #94, на стороне ODS: что повторный разбор
|
||||
того же `_load_id` не двоит версии заказа и не меняет `_load_ts` — критерии
|
||||
целиком в [спецификации приёма заказов](../architecture/orders/ingestion.md),
|
||||
раздел «Риски и проверка».
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
|
||||
**Работники** — без расписания и без паузы, запускаются руками:
|
||||
|
||||
- `world_next_day` — день пачкой; параметр «сколько дней» (умолчание 1) даёт
|
||||
разгон вперёд одним нажимом;
|
||||
- `world_next_day` — день пачкой; параметр «сколько дней» (умолчание 1, потолок
|
||||
7) даёт разгон вперёд одним нажимом. Потолок — это и есть названное желание
|
||||
«уедь на неделю сейчас», и ровно тот разгон, который забор заказов забирает
|
||||
одним чтением ([ADR 0008](0008-order-ingestion.md));
|
||||
- `world_live_day` — один день в темпе живого режима.
|
||||
|
||||
**Выключатель** — без своей работы: `world_live` несёт расписание около двадцати
|
||||
@@ -99,6 +101,10 @@
|
||||
которого стенд не поднимает; заводить службу ради одного ожидания дороже
|
||||
занятого слота.
|
||||
|
||||
Приросший к работникам забор заказов (#93, [ADR 0008](0008-order-ingestion.md))
|
||||
делает из двух занятых слотов три: пока идёт забор, ждут выключатель и сам
|
||||
работник, а третий слот занимает задача `orders_ingest`.
|
||||
|
||||
**Пакетная автоматика отложена, потому что своего желания у неё пока одно.**
|
||||
«Шагни и стой» закрывает кнопка, «уедь на неделю сейчас» — параметр «сколько
|
||||
дней», «живи, пока я смотрю» — выключатель. Ей остаётся «едь сам быстрее, чем
|
||||
|
||||
Reference in New Issue
Block a user