docs(storage): связность восстановлена, форма дат на проводе задана

- Зачем:
  - холодное ревью связности нашло девять мест, где вставленный текст спорит с
    соседним; отдельно вскрылось, что представление дат в JSON не зафиксировано
    нигде, а #43 обязан его знать раньше, чем #41 напишет сериализатор.
- Что:
  - гарантия приёма переписана: после снятия синхронной вставки «хотя бы один
    раз» стало неправдой — есть и окно потери, и окно дубля.
  - критерий выбора пяти опорных колонок приведён к списку, который он
    порождает; `CounterID` оговорён отдельно.
  - «переобработки у ODS нет вовсе» смягчено до пакетной: ручная вставка из
    сырья в пределах окна возможна.
  - в спеку генератора добавлена форма дат на проводе — ISO-8601, с доводом от
    читаемости слоя сырья.
  - убраны осиротевшая фраза про порядок сервисов, дубль порядка классов брака,
    устаревшая датировка сверки и ещё три следа вставок.
- Проверка:
  - make config-test

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 21:53:03 +03:00
co-authored by Claude Opus 5
parent f1254ce79d
commit 923ebad80e
4 changed files with 65 additions and 31 deletions
+15 -3
View File
@@ -315,8 +315,11 @@ CSV в репозитории (`data/catalog/products.csv`: `sku`, `name`, `cate
- **Приём строгий**: пять опорных колонок — `WatchID`, `VisitID`, `ClientID`,
`EventDate`, `UTCEventTime` — разбираются как `Nullable`, а набор ключей
сообщения сверяется с контрактным; строка с NULL среди опорных колонок
или с разошедшимся набором ключей уходит в `*_errors`. Опорными выбраны те, на
которых стоят ключ сортировки, партиция и дедупликация; остальные сорок две
или с разошедшимся набором ключей уходит в `*_errors`. Опорными выбраны те,
чья порча отравляет всё ниже по течению: идентификаторы события, визита и
посетителя, дата партиции и метка времени, по которой события упорядочиваются
внутри сессии. `CounterID` формально тоже в ключе сортировки, но на стенде он
константа, и NULL там взяться неоткуда. Остальные сорок две
достаются обычными типами — сорок семь проверок на NULL превратили бы матвью в
простыню, а присутствие и так целиком закрыто сверкой ключей. Сверка ключей —
не добавка: у массивов NULL не бывает, и пропавшее поле-массив иначе
@@ -379,7 +382,7 @@ README.
| Слой | Объект | Что это |
|---|---|---|
| Kafka | `hits`, `orders` | два топика, по 2 партиции |
| STG | `stg.hits_raw_kafka`, `stg.hits_raw` + MV; для orders — развилка этапа 3, см. раздел 12 | сырые строки, Kafka Engine на обеих нодах |
| STG | `stg.hits_raw_kafka`, `stg.hits_raw` + MV; для orders — развилка этапа 3, не решена (ниже) | сырые строки, Kafka Engine на обеих нодах |
| ODS | `ods.event` (+`_errors`) | типизированное широкое событие, ReplacingMergeTree |
| ODS | `ods.order_snapshot` (+`_errors`) | слепки заказов как приехали, партиция по `snapshot_date`, без дедупа |
| DDS | `dds.session` | сборка сессий из событий (наследник `dds.click`) |
@@ -393,6 +396,15 @@ README.
суффиксов; она же задаёт служебные колонки, нарезку и срок хранения сырья —
см. [доку хранилища](../architecture/storage.md).
Как принимаются заказы — развилка этапа 3, и она не решена. Событиям выбран
приём сырья байтами с разбором функциями ([ADR 0005](../adr/0005-event-ingestion.md));
заказам этот же способ идёт только вместе с ответом на вопрос, нужен ли им слой
сырья вообще — у них слепок, а не поток. Нужен — и типизированный чтец даст двух
чтецов на один топик, а такую схему ADR 0005 отверг; не нужен — и слои
перестают быть единообразными. Разбирать грилингом, когда дойдём до заказов;
как учебное сравнение двух способов приёма это записано и в опорных точках
раздела 12.
Состав служебных колонок задаёт дока хранилища. Спеке важны два следствия:
`ods.event` и `ods.order_snapshot` получают метку загрузки `_load_ts`, и у
`ods.event` она же служит колонкой версии ReplacingMergeTree; а таблицы