docs(storage): конвенции и приём событий выправлены после ревью
- Зачем:
- три холодных ревью и сверка с документацией ClickHouse нашли противоречия
между докой, ADR и спекой: исполнитель #37 получал два разных ответа на
один вопрос, а два утверждения о движке оказались неверными.
- Что:
- раскладка файлов DDL перестроена — сначала таблицы, матвью приёма
последней: иначе часть событий тихо минует ODS.
- синхронная вставка снята с пути приёма: настройка недостижима для потока
Kafka-движка и связывает шарды; на ETL-вставках осталась.
- у таблицы ошибок появился класс брака с порядком проверки, у сырья и
ошибок названы движки и ключи сортировки.
- в доку добавлен раздел «Что проверено»: сверенное с документацией,
проверяемое на стенде и сказанное по памяти разведены.
- в спеке выправлены источник матвью разбора, пять опорных колонок, имена
четырёх витрин и ссылка на несуществующую цель make.
- Проверка:
- make config-test
- grep по устаревшим именам файлов DDL и витрин — пусто
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -312,9 +312,13 @@ CSV в репозитории (`data/catalog/products.csv`: `sku`, `name`, `cate
|
||||
`cityHash64(order_id)`, сырьё STG — `cityHash64(сырой строки)`; полный
|
||||
список и доводы — в [доке хранилища](../architecture/storage.md). Урок:
|
||||
«какая нода читала топик — меняется между прогонами, куда легли данные — нет».
|
||||
- **Приём строгий**: обязательные поля разбираются как `Nullable`, а набор
|
||||
ключей сообщения сверяется с контрактным; строка с NULL среди обязательных
|
||||
полей или с разошедшимся набором ключей уходит в `*_errors`. Сверка ключей —
|
||||
- **Приём строгий**: пять опорных колонок — `WatchID`, `VisitID`, `ClientID`,
|
||||
`EventDate`, `UTCEventTime` — разбираются как `Nullable`, а набор ключей
|
||||
сообщения сверяется с контрактным; строка с NULL среди опорных колонок
|
||||
или с разошедшимся набором ключей уходит в `*_errors`. Опорными выбраны те, на
|
||||
которых стоят ключ сортировки, партиция и дедупликация; остальные сорок две
|
||||
достаются обычными типами — сорок семь проверок на NULL превратили бы матвью в
|
||||
простыню, а присутствие и так целиком закрыто сверкой ключей. Сверка ключей —
|
||||
не добавка: у массивов NULL не бывает, и пропавшее поле-массив иначе
|
||||
неотличимо от пустого по смыслу. На входе разбора нет вовсе — Kafka-таблица
|
||||
читает сообщение байтами, строгость целиком в матвью ODS
|
||||
@@ -331,8 +335,9 @@ CSV в репозитории (`data/catalog/products.csv`: `sku`, `name`, `cate
|
||||
легитимная GLOBAL-витрина (заказы малы).
|
||||
- **Конвейер без TRUNCATE**: поток — append-only в ReplacingMergeTree (дедуп
|
||||
через argMax); батчевая переобработка — по дневным партициям
|
||||
(`DROP/REPLACE PARTITION ON CLUSTER`); `TRUNCATE ... ON CLUSTER` остаётся
|
||||
только в `make reset`. `DROP/REPLACE PARTITION` работает только по
|
||||
(`DROP/REPLACE PARTITION ON CLUSTER`); `TRUNCATE ... ON CLUSTER` в конвейере не
|
||||
применяется вовсе — полный сброс стенда делается `make clean && make up`, то
|
||||
есть вместе с томами. `DROP/REPLACE PARTITION` работает только по
|
||||
**локальным** таблицам ON CLUSTER, не по Distributed; замена через
|
||||
DROP+INSERT неатомарна — дашборд в середине прогона честно моргает (это
|
||||
осознанная цена, не баг).
|
||||
@@ -374,7 +379,7 @@ README.
|
||||
| Слой | Объект | Что это |
|
||||
|---|---|---|
|
||||
| Kafka | `hits`, `orders` | два топика, по 2 партиции |
|
||||
| STG | `stg.hits_raw_kafka`, `stg.hits_raw` + MV; то же для orders | сырые строки, Kafka Engine на обеих нодах |
|
||||
| STG | `stg.hits_raw_kafka`, `stg.hits_raw` + MV; для orders — развилка этапа 3, см. раздел 12 | сырые строки, Kafka Engine на обеих нодах |
|
||||
| ODS | `ods.event` (+`_errors`) | типизированное широкое событие, ReplacingMergeTree |
|
||||
| ODS | `ods.order_snapshot` (+`_errors`) | слепки заказов как приехали, партиция по `snapshot_date`, без дедупа |
|
||||
| DDS | `dds.session` | сборка сессий из событий (наследник `dds.click`) |
|
||||
@@ -409,11 +414,11 @@ Kafka день переигрывается генератором заново:
|
||||
|
||||
### Витрины DM
|
||||
|
||||
- **`v_revenue_daily`** (выручка, только от заказов): `report_date`,
|
||||
- **`revenue_daily_v`** (выручка, только от заказов): `report_date`,
|
||||
`product_category` (через `dictGet` каталога + ARRAY JOIN позиций),
|
||||
`orders`, `units`, `revenue`, `aov`. Считается по заказам в статусе
|
||||
`paid`; внутри окна K число дня «дышит».
|
||||
- **`v_purchase_vs_orders`** (сверка): FULL OUTER GLOBAL JOIN по
|
||||
- **`purchase_vs_orders_v`** (сверка): FULL OUTER GLOBAL JOIN по
|
||||
`purchaseID = order_id`; колонки: `order_day`, `order_id`,
|
||||
`declared_revenue` (клиент), `items_total` (бэкенд, сравнимая база — не
|
||||
`total`: промокод и доставка клиенту не видны), `status`, `mismatch_class`
|
||||
@@ -427,10 +432,10 @@ Kafka день переигрывается генератором заново:
|
||||
(шестое значение `mismatch_class`, вне приоритетов расхождений). После
|
||||
закрытия окна K таких строк не остаётся — сироты исключены построением
|
||||
(раздел 4).
|
||||
- **`v_utm_effectiveness`** — остаётся клиентской (атрибуция по трекеру);
|
||||
- **`utm_effectiveness_v`** — остаётся клиентской (атрибуция по трекеру);
|
||||
счётчики `purchases`/`add_to_carts` оживают из таксономии, добавляется
|
||||
`declared_revenue` по UTM.
|
||||
- **`v_daily_traffic`** — расширяется парой «посетители» / «известные
|
||||
- **`daily_traffic_v`** — расширяется парой «посетители» / «известные
|
||||
пользователи» (обогащение через `dds.identity_map`, локальное соединение
|
||||
по ключу ко-локации).
|
||||
- `events_enriched_v`, `top_pages_daily_v`, `session_overview_v`,
|
||||
@@ -498,7 +503,7 @@ v2 стартует пустым, поэтому объём ниже — это
|
||||
`cancelled`) — на статусе `paid` стоит «дыхание» выручки; сужение до пары
|
||||
`created`/`cancelled` — запасной ход, если генератор заказов окажется дороже
|
||||
ожиданий. Связка: если срез 1 сработает, определение выручки в
|
||||
`v_revenue_daily` придётся сменить с «заказы в статусе `paid`» на «все
|
||||
`revenue_daily_v` придётся сменить с «заказы в статусе `paid`» на «все
|
||||
неотменённые заказы».
|
||||
- **Отступление от порядка #15**: резолюция предписывала резать в порядке
|
||||
1 → 2 → 3, спека применяет 3 и 2, а 1 держит в резерве. Довод: срезы 3 и 2
|
||||
@@ -569,8 +574,15 @@ smoke-проверки, а не «дашборд зелёный». Это мин
|
||||
- Точная форма `ORDER BY` ODS-таблиц (выражение `intHash32` в ключе
|
||||
ReplacingMergeTree).
|
||||
- `RawBLOB` в Kafka-движке даёт ровно одну строку на сообщение (ADR 0005).
|
||||
- Матвью, привязанная к локальной таблице, срабатывает, когда строки приходят
|
||||
вставкой через `Distributed`: на этом стоит цепочка STG → ODS (ADR 0005).
|
||||
Проверять первым, до написания DDL: если сообщения склеятся, переделывать
|
||||
придётся решение, а не запрос. Запасной формат — `LineAsString`.
|
||||
- Тип виртуальной колонки `_timestamp` у Kafka-движка: обнуляемость и
|
||||
разрядность (секунды против миллисекунд) — от этого зависит объявление
|
||||
`kafka_timestamp` в таблице сырья.
|
||||
- Матвью с источником-`Distributed` срабатывает на вставку именно в эту
|
||||
распределённую таблицу, до раскладки по шардам: на этом стоит цепочка
|
||||
STG → ODS (ADR 0005). Проверено владельцем на рабочих проектах, в документации
|
||||
ClickHouse этот случай не описан.
|
||||
- Форма именованного кортежа в `JSONExtract` с `Nullable`-членами — ею
|
||||
сворачиваются 47 вызовов в один, если разбор окажется дорогим (ADR 0005).
|
||||
- Размер артефакта эталонного мира после пересборки.
|
||||
@@ -628,8 +640,16 @@ v2, этап 0).
|
||||
`count()`, а в бою — нет; частый вопрос на собеседованиях;
|
||||
- два способа принять топик, рядом на одном стенде: сырьё байтами с разбором
|
||||
функциями (`hits`, [ADR 0005](../adr/0005-event-ingestion.md)) против
|
||||
типизированного чтеца с `kafka_handle_error_mode` (заказы, этап 3) —
|
||||
сравнение цены и наблюдаемости как задание;
|
||||
типизированного чтеца с `kafka_handle_error_mode` — сравнение цены и
|
||||
наблюдаемости как задание. **Развилка этапа 3, не решена**: типизированный
|
||||
чтец идёт заказам только вместе с ответом на вопрос, нужен ли им слой сырья.
|
||||
Нужен — и чтецов на один топик станет два, а эту схему ADR 0005 отверг; не
|
||||
нужен — и слои перестают быть единообразными. Разбирать грилингом, когда
|
||||
дойдём до заказов;
|
||||
- матвью как рабочий механизм, а не диковина: их видно на приёме и на сборке
|
||||
ODS, а пакетная работа начинается выше. Отдельным заданием — как читать из ODS
|
||||
последние версии, через `FINAL` или оконной функцией: что нагляднее, решаем на
|
||||
месте;
|
||||
- лекция про идентичность «как в бою»: `setUserID` и first-party id,
|
||||
детерминированная против вероятностной склейки, identity graph,
|
||||
кросс-девайс, CDP — с рамкой «мы склеили через транзакции, потому что трекер
|
||||
|
||||
Reference in New Issue
Block a user