docs(adr): решён приём заказов — пакетный забор слепка
- Зачем:
- развилка этапа 3 стояла нерешённой прямо в разделе 7 мастер-спеки: нужен
ли слепку слой сырья и как заказы попадают из топика в хранилище (#70).
- Что:
- заведён ADR 0008 — байтовый чтец без матвью, слой сырья у заказов
остаётся, в ods.order_snapshot пишет шаг Airflow заменой партиции; топик
orders в одну партицию, чтец на clickhouse-01 без ON CLUSTER.
- мастер-спека приведена в соответствие, разделы 6, 7, 9, 11, 12: сравнение
двух приёмов переписано на «поток против слепка», сенсор дневного батча
снят, первый даг переехал с этапа 5 на этап 3.
- в CONTEXT.md заведены «слепок», «окно изменяемости», «пакетный забор».
- Проверка:
- решение сверено по документации ClickHouse через MCP Context7 12 августа
2026 года; что осталось замерить на стенде — списком в конце ADR 0008.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+18
@@ -147,3 +147,21 @@ _Избегать_: описание схемы, документация кон
|
|||||||
**Проигрыватель**:
|
**Проигрыватель**:
|
||||||
Компонент доставки готового потока дня в приёмник. Два режима: пакетный
|
Компонент доставки готового потока дня в приёмник. Два режима: пакетный
|
||||||
(пачкой, без темпа) и живой день.
|
(пачкой, без темпа) и живой день.
|
||||||
|
|
||||||
|
**Слепок**:
|
||||||
|
Полная выгрузка заказов окна изменяемости, снятая бэкендом на границе суток:
|
||||||
|
состояние заказов на этот момент, а не поток их изменений. Один заказ
|
||||||
|
приезжает в стольких слепках, сколько дней окна он прожил.
|
||||||
|
|
||||||
|
**Окно изменяемости**:
|
||||||
|
Сколько модельных дней заказ ещё может измениться и потому продолжает ездить
|
||||||
|
в слепках. Константа мира: семь дней. За окном заказ замёрз, выручка дня
|
||||||
|
перестала «дышать».
|
||||||
|
|
||||||
|
**Пакетный забор**:
|
||||||
|
Способ приёма топика, при котором данные тянет запрос по команде, а не
|
||||||
|
матвью непрерывно. Так стенд принимает заказы: у слепка есть начало и конец,
|
||||||
|
и забирать его уместно тогда, когда он собран целиком. Противоположность —
|
||||||
|
потоковый приём событий.
|
||||||
|
_Избегать_: путать с приёмником и пакетным режимом — те про сторону
|
||||||
|
генератора, про отправку; забор — про сторону хранилища, про чтение.
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
# ADR 0008. Приём заказов: пакетный забор слепка, инициируемый Airflow
|
||||||
|
|
||||||
|
Дата: 12 августа 2026 года. Статус: принято. Реализация — отдельным тикетом.
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
Топик `orders` читает Kafka-таблица формата `RawBLOB` — та же форма чтеца, что
|
||||||
|
у событий ([ADR 0005](0005-event-ingestion.md)). Матвью к ней не привязана.
|
||||||
|
Сырьё забирает пакетный шаг, которым управляет Airflow: он вставляет
|
||||||
|
прочитанное в `stg.orders_raw_dist`, разбирает его в типизированный слепок и
|
||||||
|
заменяет партицию дня в `ods.order_snapshot`. Тот же даг проигрывает модельный
|
||||||
|
день генератором, поэтому переливается ровно то, что он положил в топик.
|
||||||
|
|
||||||
|
Прямое чтение из Kafka-движка требует двух настроек, и вторая не очевидна:
|
||||||
|
`stream_like_engine_allow_direct_select = 1` разрешает читать чтеца запросом,
|
||||||
|
а `kafka_commit_on_select = 1` на самой таблице заставляет запрос коммитить
|
||||||
|
офсеты. По умолчанию прямое чтение их не коммитит — без этой настройки каждый
|
||||||
|
прогон забирает один и тот же слепок заново.
|
||||||
|
|
||||||
|
Матвью к чтецу не привязана не по вкусу, а по устройству: при привязанной
|
||||||
|
матвью прямое чтение остаётся запрещённым независимо от первой настройки. Два
|
||||||
|
способа приёма взаимоисключающи по построению, и выбрать половину каждого
|
||||||
|
нельзя.
|
||||||
|
|
||||||
|
У топика **одна партиция**, а чтец объявлен **только на `clickhouse-01` и без
|
||||||
|
`ON CLUSTER`**. Пишет пакетный шаг по-прежнему в распределённую таблицу, так
|
||||||
|
что раскладка по шардам не меняется: ключ у сырья прежний, `cityHash64` сырой
|
||||||
|
строки.
|
||||||
|
|
||||||
|
Слои от этого получают разные роли, и каждая своя:
|
||||||
|
|
||||||
|
- `stg.orders_raw` — байты как приехали, срок жизни и метаданные доставки как
|
||||||
|
у сырья событий; повторная заливка дня честно удваивает строки, как и там;
|
||||||
|
- `ods.order_snapshot` — типизированный слепок дня, идемпотентный **заменой
|
||||||
|
партиции** `snapshot_date`, ровно как обещает раздел 2 мастер-спеки;
|
||||||
|
- `dds.order` — дедуп до последней версии через `argMax`, без изменений.
|
||||||
|
|
||||||
|
Сенсора дневного батча нет. Ждать нечего: производитель и потребитель слепка
|
||||||
|
живут в одном даге.
|
||||||
|
|
||||||
|
## Почему
|
||||||
|
|
||||||
|
**Слепок — не поток, и приём обязан это признать.** События приезжают
|
||||||
|
непрерывно, и матвью, тянущая их на лету, — честная форма для непрерывного.
|
||||||
|
Заказы приезжают раз в модельный день целой выгрузкой окна изменяемости; у
|
||||||
|
такой доставки есть начало и конец, и забирать её уместно по команде, а не
|
||||||
|
подписью на бесконечность. Стенд от этого получает не два оттенка одного
|
||||||
|
приёма, а два разных режима — push и pull, — и каждый стоит там, где ему место
|
||||||
|
по природе источника. Именно это сравнение раздел 12 мастер-спеки и заказывал
|
||||||
|
опорной точкой; прежняя его формулировка противопоставляла байтового чтеца
|
||||||
|
типизированному, то есть две формы одного и того же приёма, и переписана.
|
||||||
|
|
||||||
|
**Почему не типизированный чтец прямо в `ods.order_snapshot`.** Он давал бы
|
||||||
|
строгий приём средствами движка и живое сравнение двух чтецов даром, но ODS
|
||||||
|
перестал бы быть надстройкой над STG и стал бы вторым входом с шины. Ровно эту
|
||||||
|
схему ADR 0005 отверг для событий, и повод здесь тот же: на стенде слои и есть
|
||||||
|
предмет изучения.
|
||||||
|
|
||||||
|
**Почему не матвью из сырья в ODS, как у событий.** Тогда `ods.order_snapshot`
|
||||||
|
получил бы те же свойства, что `stg.orders_raw`: «как приехало», без дедупа,
|
||||||
|
дубли при повторной заливке законны. Два слоя подряд с одной ролью — один
|
||||||
|
лишний. Хуже того, обещание идемпотентности из раздела 2 повисло бы ни на чём:
|
||||||
|
матвью партиций не заменяет, а у ODS, в отличие от сырья, срока жизни нет, и
|
||||||
|
удвоение слепка жило бы вечно. При пакетном шаге каждый слой отрабатывает
|
||||||
|
своё, а замену партиции делает тот, кто данные и принёс.
|
||||||
|
|
||||||
|
**Почему чтец остался байтовым.** Пулл снял возражение про второй вход с шины,
|
||||||
|
и типизированный чтец снова стал допустим — но тогда между двумя приёмами
|
||||||
|
стенда менялись бы сразу две переменные, и сравнение перестало бы читаться.
|
||||||
|
Меняется одна: push против pull. Вдобавок `RawBLOB` сохраняет за слепком то же,
|
||||||
|
что даёт событиям, — колонку, которую менти открывает в обычном клиенте и
|
||||||
|
читает глазами, и возможность переразобрать сырьё, не переигрывая день.
|
||||||
|
Свойства этого формата уже сняты живыми запросами при исполнении #37 и #43;
|
||||||
|
менять его здесь значило бы платить второй раз за уже купленное.
|
||||||
|
|
||||||
|
**Почему одна партиция и один чтец.** Вторая партиция ничего не покупает:
|
||||||
|
слепок дня — порядка полутора тысяч строк, параллелизм не нужен. А урок «какая
|
||||||
|
нода читала топик» при пулле мёртв в любом случае — читает та нода, которую
|
||||||
|
спросили. То есть вторая партиция продаёт единственный настоящий риск схемы:
|
||||||
|
половина слепка застревает у ноды, к которой запрос не пришёл. Одна партиция
|
||||||
|
риск смягчает, но не снимает — брокер отдаст её любому из двух потребителей;
|
||||||
|
снимает его единственный чтец. У Airflow подготовлено одно подключение, к
|
||||||
|
`clickhouse-01`, — там чтецу и место.
|
||||||
|
|
||||||
|
Объявление без `ON CLUSTER` — не оговорка к конвенции, а её первое осознанное
|
||||||
|
исключение: у пулла один тянущий по определению. Заодно это контрпример к
|
||||||
|
рефлексу «везде `ON CLUSTER`»: приставка не ритуал, а решение.
|
||||||
|
|
||||||
|
**Почему не `Distributed` поверх Kafka.** Один запрос к распределённой таблице
|
||||||
|
над двумя чтецами осушил бы обе ноды разом и снял бы вопрос о партициях.
|
||||||
|
Механически это, скорее всего, работает — `Distributed` просто просит каждый
|
||||||
|
шард выполнить локальное чтение по имени таблицы, — но документация о такой
|
||||||
|
связке молчит: ни поддержки, ни запрета. Цена молчания высока. Офсеты
|
||||||
|
коммитятся на каждом шарде в момент чтения, а вставка идёт следом на
|
||||||
|
инициаторе: упала вставка — потеряны обе половины, а не одна. Недоступный шард
|
||||||
|
даёт худший из возможных исходов — тихо приехавшую половину слепка. Чинить
|
||||||
|
такое пришлось бы чтением исходников. И учебная цена своя: менти обязан
|
||||||
|
расшифровать конструкцию, которой нет ни в документации, ни в бою, и получает
|
||||||
|
за это трюк. При одной партиции и одном чтеце она не нужна вовсе.
|
||||||
|
|
||||||
|
**Что отвергнуто ещё.** Сенсор дневного батча из раздела 9 мастер-спеки: у
|
||||||
|
топика нет сигнала «всё», и сенсор ловил бы момент, которого не существует, —
|
||||||
|
а раз генератор и переливка в одном даге, ждать нечего по построению. Лаба,
|
||||||
|
поднимающая типизированного чтеца во второй группе потребителей ради того же
|
||||||
|
сравнения: она понадобилась бы, останься сравнение невыполненным, но push
|
||||||
|
против pull даёт его живым и постоянным.
|
||||||
|
|
||||||
|
## Следствия
|
||||||
|
|
||||||
|
Урок про виртуальные колонки — «какая нода читала топик, меняется между
|
||||||
|
прогонами» — остаётся целиком за событиями. У заказов `consumer_host` всегда
|
||||||
|
один и тот же, и это честная разница двух режимов, а не потеря: при пулле
|
||||||
|
читает тот, кого спросили.
|
||||||
|
|
||||||
|
Гарантий приёма у заказов не больше, чем у событий, но последствия мягче.
|
||||||
|
Офсеты коммитятся при чтении, вставка идёт следом — упавшая вставка теряет
|
||||||
|
пачку. У потока такая потеря невосстановима, у слепка её лечит следующий день:
|
||||||
|
окно изменяемости K = 7 привезёт те же заказы заново.
|
||||||
|
|
||||||
|
Этап 3 забирает у этапа 5 первый настоящий даг. Раздел 9 мастер-спеки отдавал
|
||||||
|
даги этапу 5 целиком; приём заказов без дага не существует, поэтому порядок
|
||||||
|
меняется. `etl_pipeline` остаётся за этапом 5.
|
||||||
|
|
||||||
|
Расхождения с мастер-спекой, внесённые тем же коммитом: раздел 6 (чтец заказов
|
||||||
|
живёт на одной ноде и без матвью), раздел 7 (развилка закрыта, у `orders` одна
|
||||||
|
партиция), раздел 9 (сенсор снят, даг переехал на этап 3), раздел 12
|
||||||
|
(сравнение приёмов переформулировано).
|
||||||
|
|
||||||
|
## Что проверено
|
||||||
|
|
||||||
|
По документации ClickHouse через MCP Context7, 12 августа 2026 года.
|
||||||
|
|
||||||
|
- Прямое чтение из движков-очередей (Kafka, RabbitMQ, FileLog) запрещено
|
||||||
|
начиная с версии 21.12 и открывается настройкой
|
||||||
|
`stream_like_engine_allow_direct_select`.
|
||||||
|
- При привязанной матвью прямое чтение остаётся запрещённым и с этой
|
||||||
|
настройкой. Отсюда вывод, что два способа приёма взаимоисключающи.
|
||||||
|
- Прямое чтение офсеты по умолчанию **не** коммитит; коммит включается
|
||||||
|
настройкой `kafka_commit_on_select` на самой таблице. Это тот подводный
|
||||||
|
камень, который молчит на первом прогоне и вылезает на втором.
|
||||||
|
- Сколько строк отдаёт одно чтение, задаёт `kafka_max_block_size`. При слепке
|
||||||
|
порядка полутора тысяч строк это один блок с запасом.
|
||||||
|
- Про `Distributed` поверх Kafka документация не говорит ничего — ни
|
||||||
|
поддержки, ни запрета.
|
||||||
|
|
||||||
|
Осталось проверить при исполнении, и это работа тикета реализации: что второй
|
||||||
|
прогон подряд возвращает пусто, то есть офсеты действительно закоммичены; что
|
||||||
|
одного чтения хватает на весь слепок дня; что виртуальные колонки доставки
|
||||||
|
(`_topic`, `_partition`, `_offset`, `_timestamp_ms`) доступны при прямом чтении
|
||||||
|
— на них стоят служебные колонки сырья, см. [доку
|
||||||
|
хранилища](../architecture/storage.md); что повторная заливка дня даёт в
|
||||||
|
`ods.order_snapshot` тот же счёт, а не удвоенный.
|
||||||
@@ -307,12 +307,17 @@ CSV в репозитории (`data/catalog/products.csv`: `sku`, `name`, `cate
|
|||||||
- Роли нод: нода 1 — инициатор DDL и подключение Airflow; **Superset — на
|
- Роли нод: нода 1 — инициатор DDL и подключение Airflow; **Superset — на
|
||||||
ноду 2**. Это осознанная ловушка правильных ошибок: забытый ON CLUSTER или
|
ноду 2**. Это осознанная ловушка правильных ошибок: забытый ON CLUSTER или
|
||||||
VIEW поверх локальной таблицы проявляются в дашборде сами.
|
VIEW поверх локальной таблицы проявляются в дашборде сами.
|
||||||
- **Приём Kafka**: Kafka-таблицы и MV — на обеих нодах, одна consumer group,
|
- **Приём Kafka**: у событий — Kafka-таблицы и MV на обеих нодах, одна
|
||||||
2 партиции на топик; MV пишут в Distributed-цели. Раскладку решает ключ:
|
consumer group, 2 партиции на топик `hits`; MV пишут в Distributed-цели.
|
||||||
|
У заказов приём другого рода — пакетный забор без MV, чтец на одной ноде,
|
||||||
|
топик `orders` в одну партицию ([ADR 0008](../adr/0008-order-ingestion.md)).
|
||||||
|
Раскладку в обоих случаях решает ключ:
|
||||||
события — по `cityHash64(ClientID)` (см. 1.3), заказы —
|
события — по `cityHash64(ClientID)` (см. 1.3), заказы —
|
||||||
`cityHash64(order_id)`, сырьё STG — `cityHash64(сырой строки)`; полный
|
`cityHash64(order_id)`, сырьё STG — `cityHash64(сырой строки)`; полный
|
||||||
список и доводы — в [доке хранилища](../architecture/storage.md). Урок:
|
список и доводы — в [доке хранилища](../architecture/storage.md). Урок
|
||||||
«какая нода читала топик — меняется между прогонами, куда легли данные — нет».
|
«какая нода читала топик — меняется между прогонами, куда легли данные —
|
||||||
|
нет» живёт на событиях: при пакетном заборе читает та нода, которую
|
||||||
|
спросили.
|
||||||
- **Приём строгий**: пять опорных колонок — `WatchID`, `VisitID`, `ClientID`,
|
- **Приём строгий**: пять опорных колонок — `WatchID`, `VisitID`, `ClientID`,
|
||||||
`EventDate`, `UTCEventTime` — разбираются как `Nullable`, а набор ключей
|
`EventDate`, `UTCEventTime` — разбираются как `Nullable`, а набор ключей
|
||||||
сообщения сверяется с контрактным; строка с NULL среди опорных колонок
|
сообщения сверяется с контрактным; строка с NULL среди опорных колонок
|
||||||
@@ -382,8 +387,9 @@ README.
|
|||||||
|
|
||||||
| Слой | Объект | Что это |
|
| Слой | Объект | Что это |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Kafka | `hits`, `orders` | два топика, по 2 партиции |
|
| Kafka | `hits`, `orders` | два топика: `hits` — 2 партиции, `orders` — одна |
|
||||||
| STG | `stg.hits_raw_kafka`, `stg.hits_raw` + MV; для orders — развилка этапа 3, не решена (ниже) | сырые строки, Kafka Engine на обеих нодах |
|
| STG | `stg.hits_raw_kafka`, `stg.hits_raw` + MV | сырые строки событий, Kafka Engine на обеих нодах |
|
||||||
|
| STG | `stg.orders_raw_kafka`, `stg.orders_raw`, без MV | сырые строки слепка; чтец на ноде 1, забирает пакетный шаг |
|
||||||
| ODS | `ods.event` (+`_errors`) | типизированное широкое событие, ReplacingMergeTree |
|
| ODS | `ods.event` (+`_errors`) | типизированное широкое событие, ReplacingMergeTree |
|
||||||
| ODS | `ods.order_snapshot` (+`_errors`) | слепки заказов как приехали, партиция по `snapshot_date`, без дедупа |
|
| ODS | `ods.order_snapshot` (+`_errors`) | слепки заказов как приехали, партиция по `snapshot_date`, без дедупа |
|
||||||
| DDS | `dds.session` | сборка сессий из событий (наследник `dds.click`) |
|
| DDS | `dds.session` | сборка сессий из событий (наследник `dds.click`) |
|
||||||
@@ -397,14 +403,16 @@ README.
|
|||||||
суффиксов; она же задаёт служебные колонки, нарезку и срок хранения сырья —
|
суффиксов; она же задаёт служебные колонки, нарезку и срок хранения сырья —
|
||||||
см. [доку хранилища](../architecture/storage.md).
|
см. [доку хранилища](../architecture/storage.md).
|
||||||
|
|
||||||
Как принимаются заказы — развилка этапа 3, и она не решена. Событиям выбран
|
Заказы принимаются **пакетным забором** ([ADR
|
||||||
приём сырья байтами с разбором функциями ([ADR 0005](../adr/0005-event-ingestion.md));
|
0008](../adr/0008-order-ingestion.md)): чтец топика байтовый, как у событий, но
|
||||||
заказам этот же способ идёт только вместе с ответом на вопрос, нужен ли им слой
|
матвью к нему не привязана, и сырьё забирает шаг, которым управляет Airflow —
|
||||||
сырья вообще — у них слепок, а не поток. Нужен — и типизированный чтец даст двух
|
он же вставляет прочитанное в `stg.orders_raw`, разбирает в типизированный
|
||||||
чтецов на один топик, а такую схему ADR 0005 отверг; не нужен — и слои
|
слепок и заменяет партицию дня в `ods.order_snapshot`. Тот же даг проигрывает
|
||||||
перестают быть единообразными. Разбирать грилингом, когда дойдём до заказов;
|
модельный день генератором, поэтому переливается ровно то, что он положил в
|
||||||
как учебное сравнение двух способов приёма это записано и в опорных точках
|
топик. Слой сырья у заказов остаётся: без него `ods.order_snapshot` повторил бы
|
||||||
раздела 12.
|
его роль, а обещание идемпотентности повисло бы — матвью партиций не заменяет.
|
||||||
|
Стенд получает от этого два режима приёма рядом, поток и слепок, и сравнение
|
||||||
|
из опорных точек раздела 12 переформулировано под них.
|
||||||
|
|
||||||
Состав служебных колонок задаёт дока хранилища. Спеке важны два следствия:
|
Состав служебных колонок задаёт дока хранилища. Спеке важны два следствия:
|
||||||
`ods.event` и `ods.order_snapshot` получают метку загрузки `_load_ts`, и у
|
`ods.event` и `ods.order_snapshot` получают метку загрузки `_load_ts`, и у
|
||||||
@@ -495,7 +503,7 @@ v2 стартует пустым, поэтому объём ниже — это
|
|||||||
| Генератор | с нуля: модель v1 не переносится (другая модель данных, плюс известные проблемы производительности v1); широкое событие, таксономия, анонимность, N:1, заказы слепками, расхождения A–D, каталог; масштаб — ~4–5 тыс. строк с тестами | L |
|
| Генератор | с нуля: модель v1 не переносится (другая модель данных, плюс известные проблемы производительности v1); широкое событие, таксономия, анонимность, N:1, заказы слепками, расхождения A–D, каталог; масштаб — ~4–5 тыс. строк с тестами | L |
|
||||||
| Инфраструктура | compose: 2 ноды CH + keeper + остальной стенд; конфиги кластера, макросы; make/скрипты | M — ~10–12 файлов |
|
| Инфраструктура | compose: 2 ноды CH + keeper + остальной стенд; конфиги кластера, макросы; make/скрипты | M — ~10–12 файлов |
|
||||||
| SQL | DDL по слоям и ролям (ON CLUSTER, Replicated*, Distributed; раскладка файлов — в доке хранилища) + трансформации событий, заказов, identity, сверки + словарь | L — ~12–15 файлов, главная сложность |
|
| SQL | DDL по слоям и ролям (ON CLUSTER, Replicated*, Distributed; раскладка файлов — в доке хранилища) + трансформации событий, заказов, identity, сверки + словарь | L — ~12–15 файлов, главная сложность |
|
||||||
| Airflow | DAG'и по образцу v1: etl_pipeline (партиционная переобработка, ожидание дневного батча заказов — сенсор/Datasets), world_init/next_day, helpers | M — ~5–6 файлов |
|
| Airflow | DAG'и по образцу v1: etl_pipeline (партиционная переобработка), world_init/next_day, helpers; первый даг — переливка слепка — приходит раньше, этапом 3 | M — ~5–6 файлов |
|
||||||
| Superset | датасеты + дашборд с тремя новыми сюжетами | M — 2 файла |
|
| Superset | датасеты + дашборд с тремя новыми сюжетами | M — 2 файла |
|
||||||
| Эталонный мир | пересборка снимка на месте, счётчики описи, чек-скрипты | M–L |
|
| Эталонный мир | пересборка снимка на месте, счётчики описи, чек-скрипты | M–L |
|
||||||
| Мониторинг | дашборды Grafana «данные», «кластер», «запросы»; ClickHouse источником данных, панели на SQL; Prometheus тонким полом (ADR 0002) | M — конфиги и дашборды |
|
| Мониторинг | дашборды Grafana «данные», «кластер», «запросы»; ClickHouse источником данных, панели на SQL; Prometheus тонким полом (ADR 0002) | M — конфиги и дашборды |
|
||||||
@@ -537,10 +545,11 @@ v2 стартует пустым, поэтому объём ниже — это
|
|||||||
целиком). В конце этапа фиксируется маленький стартовый мир для
|
целиком). В конце этапа фиксируется маленький стартовый мир для
|
||||||
стабильных приёмок следующих этапов (полная пересборка эталонного мира —
|
стабильных приёмок следующих этапов (полная пересборка эталонного мира —
|
||||||
отдельный этап 7).
|
отдельный этап 7).
|
||||||
3. Заказы и каталог: генератор слепков, STG/ODS/DDS заказа, словарь.
|
3. Заказы и каталог: генератор слепков, STG/ODS/DDS заказа, словарь и первый
|
||||||
|
настоящий даг — проигрыш модельного дня плюс переливка слепка ([ADR
|
||||||
|
0008](../adr/0008-order-ingestion.md)).
|
||||||
4. Трансформации и витрины: сессии, identity_map, выручка, сверка A+C.
|
4. Трансформации и витрины: сессии, identity_map, выручка, сверка A+C.
|
||||||
5. Airflow: `etl_pipeline` (партиционная переобработка, ожидание дневного
|
5. Airflow: `etl_pipeline` (партиционная переобработка).
|
||||||
батча заказов — сенсор/Datasets).
|
|
||||||
6. Расхождения B+D и опоздания; счётчики описи.
|
6. Расхождения B+D и опоздания; счётчики описи.
|
||||||
7. Эталонный мир: опись и пересборка снимка, чек-скрипты; CI-генерация
|
7. Эталонный мир: опись и пересборка снимка, чек-скрипты; CI-генерация
|
||||||
на amd64 и arm64.
|
на amd64 и arm64.
|
||||||
@@ -603,9 +612,13 @@ Kafka Engine на двух нодах снят с этого списка при
|
|||||||
- Размер артефакта эталонного мира после пересборки.
|
- Размер артефакта эталонного мира после пересборки.
|
||||||
- Спорные API (Airflow Datasets/сенсоры, ClickHouse DDL) — перед кодом
|
- Спорные API (Airflow Datasets/сенсоры, ClickHouse DDL) — перед кодом
|
||||||
сверять через MCP Context7 (правило AGENTS.md).
|
сверять через MCP Context7 (правило AGENTS.md).
|
||||||
- Airflow 3.x: версия фиксируется на этапе 1 (каркас); DAG'и этапа 5 пишутся
|
- Airflow 3.x: версия фиксируется на этапе 1 (каркас); DAG'и пишутся под API
|
||||||
под API третьей версии (Datasets → Assets) — актуальные операторы и сенсоры
|
третьей версии (Datasets → Assets) — актуальные операторы проверить через
|
||||||
проверить через Context7.
|
Context7. Первый даг приходит этапом 3, а не 5 ([ADR
|
||||||
|
0008](../adr/0008-order-ingestion.md)).
|
||||||
|
- Пакетный забор слепка из Kafka: коммит офсетов прямым чтением, хватает ли
|
||||||
|
одного чтения на слепок дня, доступны ли при нём виртуальные колонки
|
||||||
|
доставки — список и ответы в [ADR 0008](../adr/0008-order-ingestion.md).
|
||||||
- Генератор: рабочее решение — Python с производительной архитектурой
|
- Генератор: рабочее решение — Python с производительной архитектурой
|
||||||
(батчевая генерация вместо посточной, быстрая JSON-сериализация,
|
(батчевая генерация вместо посточной, быстрая JSON-сериализация,
|
||||||
распараллеливание по модельным дням). Читаемость генератора для менти —
|
распараллеливание по модельным дням). Читаемость генератора для менти —
|
||||||
@@ -653,14 +666,14 @@ v2, этап 0).
|
|||||||
синтетическая постановка — осознанный приём;
|
синтетическая постановка — осознанный приём;
|
||||||
- лекция «`Sign` и CollapsingMergeTree»: почему на стенде `sum(Sign)` =
|
- лекция «`Sign` и CollapsingMergeTree»: почему на стенде `sum(Sign)` =
|
||||||
`count()`, а в бою — нет; частый вопрос на собеседованиях;
|
`count()`, а в бою — нет; частый вопрос на собеседованиях;
|
||||||
- два способа принять топик, рядом на одном стенде: сырьё байтами с разбором
|
- два способа принять топик, рядом на одном стенде: **поток против слепка,
|
||||||
функциями (`hits`, [ADR 0005](../adr/0005-event-ingestion.md)) против
|
push против pull**. События тянет матвью на лету ([ADR
|
||||||
типизированного чтеца с `kafka_handle_error_mode` — сравнение цены и
|
0005](../adr/0005-event-ingestion.md)), слепок заказов забирает пакетный шаг
|
||||||
наблюдаемости как задание. **Развилка этапа 3, не решена**: типизированный
|
по команде дага ([ADR 0008](../adr/0008-order-ingestion.md)); чтец в обоих
|
||||||
чтец идёт заказам только вместе с ответом на вопрос, нужен ли им слой сырья.
|
случаях байтовый, так что различает их ровно режим. Сравнение цены,
|
||||||
Нужен — и чтецов на один топик станет два, а эту схему ADR 0005 отверг; не
|
наблюдаемости и того, чем платит каждый режим, — как задание. Третий способ,
|
||||||
нужен — и слои перестают быть единообразными. Разбирать грилингом, когда
|
типизированный чтец с `kafka_handle_error_mode`, на стенде не живёт: он
|
||||||
дойдём до заказов;
|
отвергнут обоими ADR, и остаётся материалом для рассказа;
|
||||||
- матвью как рабочий механизм, а не диковина: их видно на приёме и на сборке
|
- матвью как рабочий механизм, а не диковина: их видно на приёме и на сборке
|
||||||
ODS, а пакетная работа начинается выше. Отдельным заданием — как читать из ODS
|
ODS, а пакетная работа начинается выше. Отдельным заданием — как читать из ODS
|
||||||
последние версии, через `FINAL` или оконной функцией: что нагляднее, решаем на
|
последние версии, через `FINAL` или оконной функцией: что нагляднее, решаем на
|
||||||
|
|||||||
Reference in New Issue
Block a user