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:
@@ -307,12 +307,17 @@ CSV в репозитории (`data/catalog/products.csv`: `sku`, `name`, `cate
|
||||
- Роли нод: нода 1 — инициатор DDL и подключение Airflow; **Superset — на
|
||||
ноду 2**. Это осознанная ловушка правильных ошибок: забытый ON CLUSTER или
|
||||
VIEW поверх локальной таблицы проявляются в дашборде сами.
|
||||
- **Приём Kafka**: Kafka-таблицы и MV — на обеих нодах, одна consumer group,
|
||||
2 партиции на топик; MV пишут в Distributed-цели. Раскладку решает ключ:
|
||||
- **Приём Kafka**: у событий — Kafka-таблицы и MV на обеих нодах, одна
|
||||
consumer group, 2 партиции на топик `hits`; MV пишут в Distributed-цели.
|
||||
У заказов приём другого рода — пакетный забор без MV, чтец на одной ноде,
|
||||
топик `orders` в одну партицию ([ADR 0008](../adr/0008-order-ingestion.md)).
|
||||
Раскладку в обоих случаях решает ключ:
|
||||
события — по `cityHash64(ClientID)` (см. 1.3), заказы —
|
||||
`cityHash64(order_id)`, сырьё STG — `cityHash64(сырой строки)`; полный
|
||||
список и доводы — в [доке хранилища](../architecture/storage.md). Урок:
|
||||
«какая нода читала топик — меняется между прогонами, куда легли данные — нет».
|
||||
список и доводы — в [доке хранилища](../architecture/storage.md). Урок
|
||||
«какая нода читала топик — меняется между прогонами, куда легли данные —
|
||||
нет» живёт на событиях: при пакетном заборе читает та нода, которую
|
||||
спросили.
|
||||
- **Приём строгий**: пять опорных колонок — `WatchID`, `VisitID`, `ClientID`,
|
||||
`EventDate`, `UTCEventTime` — разбираются как `Nullable`, а набор ключей
|
||||
сообщения сверяется с контрактным; строка с NULL среди опорных колонок
|
||||
@@ -382,8 +387,9 @@ README.
|
||||
|
||||
| Слой | Объект | Что это |
|
||||
|---|---|---|
|
||||
| Kafka | `hits`, `orders` | два топика, по 2 партиции |
|
||||
| STG | `stg.hits_raw_kafka`, `stg.hits_raw` + MV; для orders — развилка этапа 3, не решена (ниже) | сырые строки, Kafka Engine на обеих нодах |
|
||||
| Kafka | `hits`, `orders` | два топика: `hits` — 2 партиции, `orders` — одна |
|
||||
| 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.order_snapshot` (+`_errors`) | слепки заказов как приехали, партиция по `snapshot_date`, без дедупа |
|
||||
| DDS | `dds.session` | сборка сессий из событий (наследник `dds.click`) |
|
||||
@@ -397,14 +403,16 @@ README.
|
||||
суффиксов; она же задаёт служебные колонки, нарезку и срок хранения сырья —
|
||||
см. [доку хранилища](../architecture/storage.md).
|
||||
|
||||
Как принимаются заказы — развилка этапа 3, и она не решена. Событиям выбран
|
||||
приём сырья байтами с разбором функциями ([ADR 0005](../adr/0005-event-ingestion.md));
|
||||
заказам этот же способ идёт только вместе с ответом на вопрос, нужен ли им слой
|
||||
сырья вообще — у них слепок, а не поток. Нужен — и типизированный чтец даст двух
|
||||
чтецов на один топик, а такую схему ADR 0005 отверг; не нужен — и слои
|
||||
перестают быть единообразными. Разбирать грилингом, когда дойдём до заказов;
|
||||
как учебное сравнение двух способов приёма это записано и в опорных точках
|
||||
раздела 12.
|
||||
Заказы принимаются **пакетным забором** ([ADR
|
||||
0008](../adr/0008-order-ingestion.md)): чтец топика байтовый, как у событий, но
|
||||
матвью к нему не привязана, и сырьё забирает шаг, которым управляет Airflow —
|
||||
он же вставляет прочитанное в `stg.orders_raw`, разбирает в типизированный
|
||||
слепок и заменяет партицию дня в `ods.order_snapshot`. Тот же даг проигрывает
|
||||
модельный день генератором, поэтому переливается ровно то, что он положил в
|
||||
топик. Слой сырья у заказов остаётся: без него `ods.order_snapshot` повторил бы
|
||||
его роль, а обещание идемпотентности повисло бы — матвью партиций не заменяет.
|
||||
Стенд получает от этого два режима приёма рядом, поток и слепок, и сравнение
|
||||
из опорных точек раздела 12 переформулировано под них.
|
||||
|
||||
Состав служебных колонок задаёт дока хранилища. Спеке важны два следствия:
|
||||
`ods.event` и `ods.order_snapshot` получают метку загрузки `_load_ts`, и у
|
||||
@@ -495,7 +503,7 @@ v2 стартует пустым, поэтому объём ниже — это
|
||||
| Генератор | с нуля: модель v1 не переносится (другая модель данных, плюс известные проблемы производительности v1); широкое событие, таксономия, анонимность, N:1, заказы слепками, расхождения A–D, каталог; масштаб — ~4–5 тыс. строк с тестами | L |
|
||||
| Инфраструктура | compose: 2 ноды CH + keeper + остальной стенд; конфиги кластера, макросы; make/скрипты | M — ~10–12 файлов |
|
||||
| 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 файла |
|
||||
| Эталонный мир | пересборка снимка на месте, счётчики описи, чек-скрипты | M–L |
|
||||
| Мониторинг | дашборды Grafana «данные», «кластер», «запросы»; ClickHouse источником данных, панели на SQL; Prometheus тонким полом (ADR 0002) | M — конфиги и дашборды |
|
||||
@@ -537,10 +545,11 @@ v2 стартует пустым, поэтому объём ниже — это
|
||||
целиком). В конце этапа фиксируется маленький стартовый мир для
|
||||
стабильных приёмок следующих этапов (полная пересборка эталонного мира —
|
||||
отдельный этап 7).
|
||||
3. Заказы и каталог: генератор слепков, STG/ODS/DDS заказа, словарь.
|
||||
3. Заказы и каталог: генератор слепков, STG/ODS/DDS заказа, словарь и первый
|
||||
настоящий даг — проигрыш модельного дня плюс переливка слепка ([ADR
|
||||
0008](../adr/0008-order-ingestion.md)).
|
||||
4. Трансформации и витрины: сессии, identity_map, выручка, сверка A+C.
|
||||
5. Airflow: `etl_pipeline` (партиционная переобработка, ожидание дневного
|
||||
батча заказов — сенсор/Datasets).
|
||||
5. Airflow: `etl_pipeline` (партиционная переобработка).
|
||||
6. Расхождения B+D и опоздания; счётчики описи.
|
||||
7. Эталонный мир: опись и пересборка снимка, чек-скрипты; CI-генерация
|
||||
на amd64 и arm64.
|
||||
@@ -603,9 +612,13 @@ Kafka Engine на двух нодах снят с этого списка при
|
||||
- Размер артефакта эталонного мира после пересборки.
|
||||
- Спорные API (Airflow Datasets/сенсоры, ClickHouse DDL) — перед кодом
|
||||
сверять через MCP Context7 (правило AGENTS.md).
|
||||
- Airflow 3.x: версия фиксируется на этапе 1 (каркас); DAG'и этапа 5 пишутся
|
||||
под API третьей версии (Datasets → Assets) — актуальные операторы и сенсоры
|
||||
проверить через Context7.
|
||||
- Airflow 3.x: версия фиксируется на этапе 1 (каркас); DAG'и пишутся под API
|
||||
третьей версии (Datasets → Assets) — актуальные операторы проверить через
|
||||
Context7. Первый даг приходит этапом 3, а не 5 ([ADR
|
||||
0008](../adr/0008-order-ingestion.md)).
|
||||
- Пакетный забор слепка из Kafka: коммит офсетов прямым чтением, хватает ли
|
||||
одного чтения на слепок дня, доступны ли при нём виртуальные колонки
|
||||
доставки — список и ответы в [ADR 0008](../adr/0008-order-ingestion.md).
|
||||
- Генератор: рабочее решение — Python с производительной архитектурой
|
||||
(батчевая генерация вместо посточной, быстрая JSON-сериализация,
|
||||
распараллеливание по модельным дням). Читаемость генератора для менти —
|
||||
@@ -653,14 +666,14 @@ v2, этап 0).
|
||||
синтетическая постановка — осознанный приём;
|
||||
- лекция «`Sign` и CollapsingMergeTree»: почему на стенде `sum(Sign)` =
|
||||
`count()`, а в бою — нет; частый вопрос на собеседованиях;
|
||||
- два способа принять топик, рядом на одном стенде: сырьё байтами с разбором
|
||||
функциями (`hits`, [ADR 0005](../adr/0005-event-ingestion.md)) против
|
||||
типизированного чтеца с `kafka_handle_error_mode` — сравнение цены и
|
||||
наблюдаемости как задание. **Развилка этапа 3, не решена**: типизированный
|
||||
чтец идёт заказам только вместе с ответом на вопрос, нужен ли им слой сырья.
|
||||
Нужен — и чтецов на один топик станет два, а эту схему ADR 0005 отверг; не
|
||||
нужен — и слои перестают быть единообразными. Разбирать грилингом, когда
|
||||
дойдём до заказов;
|
||||
- два способа принять топик, рядом на одном стенде: **поток против слепка,
|
||||
push против pull**. События тянет матвью на лету ([ADR
|
||||
0005](../adr/0005-event-ingestion.md)), слепок заказов забирает пакетный шаг
|
||||
по команде дага ([ADR 0008](../adr/0008-order-ingestion.md)); чтец в обоих
|
||||
случаях байтовый, так что различает их ровно режим. Сравнение цены,
|
||||
наблюдаемости и того, чем платит каждый режим, — как задание. Третий способ,
|
||||
типизированный чтец с `kafka_handle_error_mode`, на стенде не живёт: он
|
||||
отвергнут обоими ADR, и остаётся материалом для рассказа;
|
||||
- матвью как рабочий механизм, а не диковина: их видно на приёме и на сборке
|
||||
ODS, а пакетная работа начинается выше. Отдельным заданием — как читать из ODS
|
||||
последние версии, через `FINAL` или оконной функцией: что нагляднее, решаем на
|
||||
|
||||
Reference in New Issue
Block a user