docs(ingestion): упрощены объяснения источников и приема данных
Зачем: помочь начинающему инженеру проследить событие и версию заказа от генератора до хранилища, включая ошибки и повторные поступления. Что: обновлены вводная генератора, справочники приема и модели заказа, разделы хранилища и комментарии SQL и дага. Сохранены технические условия; исполняемый код, схемы и поведение стенда не изменены. Проверка: make lint, git diff --check, 142 локальные ссылки и справка CLI. SQL без комментариев и Python AST без строк документации совпали с исходными. Генерация данных и сброс состояния не выполнялись. Refs #250
This commit is contained in:
1 parent
83434102d5
commit
75f28fe99f
13 files changed
+663
-974
No files matched your search
@@ -1,241 +1,172 @@
|
||||
# Приём заказов из Kafka в ODS
|
||||
# Прием заказов из Kafka в ODS
|
||||
|
||||
Учебный результат: менти различает версию бизнес-сущности, наблюдение источника
|
||||
и запуск загрузки, а затем читает физические версии через явную поверхность
|
||||
текущего состояния.
|
||||
|
||||
## Проблема
|
||||
|
||||
Заказы приезжают полным слепком окна изменяемости, но Kafka передаёт его
|
||||
отдельными сообщениями и не сообщает потребителю, где слепок закончился.
|
||||
Прямое чтение Kafka Engine возвращает одну порцию без признака полноты.
|
||||
Поэтому `snapshot_date` не может служить границей публикации: `REPLACE
|
||||
PARTITION` могла бы заменить партицию дня неполным набором строк.
|
||||
|
||||
Одновременно типизированный ODS не должен принимать правдоподобные значения по
|
||||
умолчанию из грязного JSON или останавливать весь пакет из-за одной строки.
|
||||
|
||||
## Цели
|
||||
|
||||
- сохранить пакетный забор как контраст потоковому приёму событий;
|
||||
- один раз принять байты в STG и независимо разложить строки на годные и брак;
|
||||
- хранить в ODS типизированные версии заказов, не публикуя слепок заменой
|
||||
партиции;
|
||||
- дать следующим слоям один корректный способ прочитать текущее состояние;
|
||||
- оставить код проверки коротким и ограничить его контрактом провода.
|
||||
|
||||
## Не входит
|
||||
|
||||
- модель заказа в DDS: её зерно, связи, материализация и способ наполнения;
|
||||
- проверка полей внутри `items`, переходов статуса и равенств денежных сумм;
|
||||
- удаление заказа по отсутствию в следующем слепке;
|
||||
- маркер конца слепка, опись ожидаемых строк и транзакция между целями ODS.
|
||||
Здесь разобран путь одного заказа: сообщение Kafka сохраняется в STG,
|
||||
проверяется и попадает либо в ODS, либо в таблицу ошибок. Следующие слои
|
||||
читают из ODS последнюю принятую версию каждого заказа.
|
||||
|
||||
## Поток данных
|
||||
|
||||
После завершения генератора Airflow один раз читает байтовый Kafka-чтец и
|
||||
записывает полученную порцию в `stg.orders_raw`. Даг зовётся `orders_ingest`, а
|
||||
запускает его шаговый даг модельного времени, который положил слепок в топик и
|
||||
ждёт конца прогона. Все строки получают `_load_id`, равный `run_id` Airflow.
|
||||
`_load_ts` вычисляется при этой записи и дальше переносится без пересчёта.
|
||||
Запрос забора лежит в
|
||||
[`sql/stg/orders_raw_load.sql`](../../../sql/stg/orders_raw_load.sql):
|
||||
`SQLExecuteQueryOperator` задаёт подключение и передаёт `run_id` как параметр
|
||||
ClickHouse `load_id`, а преобразование остаётся в SQL своего слоя.
|
||||
Источник отправляет в топик `orders` дневной слепок: по одному сообщению
|
||||
на заказ. Слепок дня D содержит состояния заказов, оформленных в дни
|
||||
D−6…D, с учетом [задержек первого появления](fate.md).
|
||||
В начале модельного времени окно короче. Выгрузку за D отправляет
|
||||
генератор при запуске дня D+1; подробности — в [описании слепка](snapshot.md).
|
||||
|
||||
Один следующий `task_id` отвечает за весь переход STG → ODS. Внутри него два
|
||||
последовательных `INSERT SELECT` читают неизменный срез по `_load_id`: первый
|
||||
пишет годные строки в `ods.order`, второй — брак в `ods.order_errors`.
|
||||
Транзакции между запросами нет. При частичном сбое Airflow повторяет весь
|
||||
`task_id`; одинаковые исходные строки и служебные метки не вычисляются заново.
|
||||
Обе вставки лежат в одном самодостаточном файле
|
||||
[`order_parse.sql`](../../../sql/ods/order_parse.sql). Airflow сначала
|
||||
шаблонизирует файл, затем делит получившийся SQL на выражения и исполняет их по
|
||||
порядку в одной задаче `parse_batch`.
|
||||
После отправки шаговый даг модельного времени запускает
|
||||
[`orders_ingest`](../../../dags/orders_ingest.py) и ждет его завершения.
|
||||
У `orders_ingest` нет расписания; его два шага выполняются по порядку:
|
||||
|
||||
Условия запросов взаимодополняющие: один общий предикат определяет годную
|
||||
строку, а ветвь брака использует его буквальное отрицание. Все функции предиката
|
||||
возвращают результат без исключения, а сам предикат всегда заканчивается в
|
||||
`true` или `false`, не в `NULL`. Постоянный классификатор между STG и ODS для
|
||||
этого не нужен. Предикат объявлен локальным макросом Jinja внутри
|
||||
`order_parse.sql` и разворачивается в оба выражения. Макрос убирает
|
||||
дублирование, но не вводит отдельный фрагмент, который нельзя исполнить
|
||||
самостоятельно.
|
||||
1. `pull_batch` читает порцию из `stg.orders_raw_kafka` и записывает ее
|
||||
через `stg.orders_raw_dist`. Содержимое сообщения остается строкой `raw`,
|
||||
рядом сохраняются координаты Kafka, `_load_id` и `_load_ts`.
|
||||
2. `parse_batch` читает сохраненные строки с этим `_load_id`. Первый
|
||||
`INSERT SELECT` пишет годные записи в `ods.order_dist`, второй —
|
||||
остальные в `ods.order_errors_dist`.
|
||||
|
||||
`_load_id` равен `run_id` Airflow и связывает строки одного запуска.
|
||||
`_load_ts` — реальное время записи в STG; при разборе оно переносится
|
||||
без пересчета. Запросы лежат в
|
||||
[`orders_raw_load.sql`](../../../sql/stg/orders_raw_load.sql) и
|
||||
[`order_parse.sql`](../../../sql/ods/order_parse.sql).
|
||||
`SQLExecuteQueryOperator` передает `run_id` параметром ClickHouse `load_id`.
|
||||
|
||||
Две вставки разбора используют одно условие годности, записанное макросом
|
||||
Jinja внутри `order_parse.sql`. Перед выполнением макрос подставляется
|
||||
в оба запроса; ветвь брака берет отрицание этого условия. Оно возвращает
|
||||
только истину или ложь и не вызывает исключений на некорректном `raw`.
|
||||
Так каждая строка сохраненной порции получает ровно один результат.
|
||||
|
||||
Транзакции между вставками нет: первая может завершиться, а вторая упасть.
|
||||
Повтор `parse_batch` выполняет обе вставки над тем же срезом STG.
|
||||
Текущее состояние заказа останется прежним, но таблица ошибок может
|
||||
получить повторные строки с теми же `_load_id` и координатами Kafka.
|
||||
Обе вставки ждут доставки строк на шарды и используют ключ целевой таблицы;
|
||||
настройки объяснены в [справочнике хранилища](../storage.md#раскладка-по-шардам).
|
||||
|
||||
## Граница строгого приёма
|
||||
|
||||
Единица решения — одна строка `stg.orders_raw`. Корень должен быть
|
||||
JSON-объектом с точным набором ключей: `order_id`, `user_id`, `status`,
|
||||
`created_at`, `updated_at`, `items_total`, `discount`, `delivery`, `total`,
|
||||
`items`, `snapshot_date`.
|
||||
Проверяется одна строка `stg.orders_raw_dist`. Корень JSON должен быть
|
||||
объектом с точным набором из одиннадцати ключей:
|
||||
`order_id`, `user_id`, `status`, `created_at`, `updated_at`,
|
||||
`items_total`, `discount`, `delivery`, `total`, `items`, `snapshot_date`.
|
||||
|
||||
Скалярные поля проверяются по типу JSON. Деньги дополнительно обязаны быть
|
||||
строками неотрицательной суммы с ровно двумя знаками после точки — минус в эту
|
||||
форму не входит; времена — строками RFC 3339 в UTC с обязательными
|
||||
миллисекундами; дата слепка — строкой `YYYY-MM-DD`. `items` проверяется только
|
||||
как JSON-массив. Каноническая форма и основания выбора зафиксированы в
|
||||
[исследовании формата](../../research/2026-08-16-order-snapshot-wire-format.md).
|
||||
| Поля | Условия приема |
|
||||
|---|---|
|
||||
| `order_id`, `status` | Строки JSON |
|
||||
| `user_id` | Целое число JSON, которое разбирается в `UInt64` |
|
||||
| `created_at`, `updated_at` | Строки UTC с миллисекундами, например `2026-06-01T12:34:56.000Z`; проверяются форма и разбор времени |
|
||||
| `items_total`, `discount`, `delivery`, `total` | Неотрицательные суммы в рублях, записанные строками с двумя знаками после точки; проверяется разбор в `Decimal(18, 2)` |
|
||||
| `snapshot_date` | Строка даты `YYYY-MM-DD`, которая проходит разбор |
|
||||
| `items` | Массив JSON; сохраняется целиком в строковой колонке |
|
||||
|
||||
Проверять все верхнеуровневые поля здесь уместно: их одиннадцать, и десять
|
||||
скалярных значений непосредственно образуют типизированную строку заказа. У
|
||||
события из 47 полей проверяются только пять опорных; переносить то сокращение на
|
||||
малый контракт заказа нет причины. Граница строгости заканчивается на форме
|
||||
провода: содержимое позиций и бизнес-инварианты намеренно остаются ниже.
|
||||
ODS проверяет формат верхнего уровня. Здесь нет проверки допустимых
|
||||
статусов, их переходов, полей внутри `items` или равенства денежных сумм.
|
||||
Позиции разбираются в [DDS](../dds/order.md); проверку их суммы участник
|
||||
добавляет в [задании ступени 5](../../labs/5-1-order-checks.md).
|
||||
Основания выбора формата — в [исследовании](../../research/2026-08-16-order-snapshot-wire-format.md).
|
||||
|
||||
Контракт источника задаёт частоту: **одной дате соответствует один слепок**. Это
|
||||
свойство всего слепка, поэтому предикат одной строки его не проверяет. Вторая
|
||||
выгрузка того же дня даст ничью по `snapshot_date`, и выбор строки потеряет
|
||||
однозначность. Модельный источник выполняет условие; в настоящей интеграции
|
||||
его фиксируют в договоре с источником
|
||||
([ADR 0014](../../adr/0014-order-version-snapshot-date.md)).
|
||||
Класс ошибки записывается в `error_class` по первому нарушенному условию:
|
||||
|
||||
Класс брака выбирается первым совпадением:
|
||||
1. `not_an_object` — корень JSON не объект;
|
||||
2. `keyset_mismatch` — набор ключей отличается от ожидаемого;
|
||||
3. `field_invalid` — тип, форма или разбор значения не прошли проверку.
|
||||
|
||||
1. `not_an_object`;
|
||||
2. `keyset_mismatch`;
|
||||
3. `field_invalid`.
|
||||
|
||||
Имя отдельного поля в класс не включается. В таблице ошибок остаются сырой
|
||||
текст, метаданные доставки и `_load_id`, поэтому единичный случай можно разобрать
|
||||
без постоянной детализации предиката.
|
||||
Имя поля в класс не входит. Для разбора конкретной ошибки в
|
||||
`ods.order_errors_dist` есть исходный `raw`, координаты Kafka и метки загрузки.
|
||||
|
||||
## Роль ODS
|
||||
|
||||
`ods.order_rep` хранит физически принятые версии в
|
||||
`ReplacingMergeTree(snapshot_date)`. Ключ сортировки — `order_id`, партиция —
|
||||
день неизменного `created_at`. `ods.order_dist` шардирует по
|
||||
`cityHash64(order_id)`: только так все версии заказа попадают на один шард и
|
||||
`FINAL` даёт корректный результат через распределённую таблицу.
|
||||
`ods.order_rep` использует `ReplicatedReplacingMergeTree(snapshot_date)`.
|
||||
Строки с одним ключом сортировки `order_id` считаются версиями одного заказа;
|
||||
остается строка с максимальной `snapshot_date`. Партиция —
|
||||
`toDate(created_at)` в UTC, ключ шардирования — `cityHash64(order_id)`.
|
||||
Неизменный `created_at` и общий ключ направляют все версии заказа
|
||||
в одну партицию и на один шард.
|
||||
|
||||
Четыре координаты отвечают на разные вопросы:
|
||||
| Колонка | На какой вопрос отвечает |
|
||||
|---|---|
|
||||
| `snapshot_date` | На конец какого модельного дня источник показал это состояние? |
|
||||
| `updated_at` | Когда запись заказа последний раз меняли в магазине? |
|
||||
| `_load_id` | Какой запуск Airflow принял сообщение? |
|
||||
| `_load_ts` | Когда сообщение записали в STG по реальным часам стенда? |
|
||||
|
||||
- `snapshot_date` — в слепке какого модельного дня источник показал строку;
|
||||
- `updated_at` — когда строку последний раз меняли в источнике;
|
||||
- `_load_id` — какой запуск Airflow принял строку;
|
||||
- `_load_ts` — когда строка приехала в хранилище.
|
||||
Например, заказ не менялся с 1 июня, но входит в слепки за 1 и 2 июня.
|
||||
`updated_at` у строк одинаков, а `snapshot_date` различается. ODS оставит
|
||||
строку за 2 июня вместе с метками ее доставки. Новая дата наблюдения
|
||||
нужна DDS для определения окна пересборки, даже если состояние не изменилось.
|
||||
|
||||
Версию выбирает `snapshot_date`: выживает строка с максимальной датой слепка
|
||||
([ADR 0014](../../adr/0014-order-version-snapshot-date.md)). Поэтому
|
||||
`snapshot_date` показывает последнее наблюдение заказа. По строкам последней
|
||||
даты слепка DDS определяет окно пересборки. `_load_id` и `_load_ts` принадлежат
|
||||
той физической строке, которую сохранило слияние, и описывают её доставку.
|
||||
Источник выпускает **один слепок на дату**. Повторная отправка дает то же
|
||||
содержимое, а более поздний слепок не возвращает устаревшее состояние.
|
||||
Эти условия не проверяются на одной строке при приеме. Если источник
|
||||
пришлет два разных состояния с одной `snapshot_date`, эта колонка
|
||||
не позволит определить, какое из них актуально.
|
||||
Обоснование версии — [ADR 0014](../../adr/0014-order-version-snapshot-date.md).
|
||||
|
||||
Схема версий стоит на дисциплине источника: слепок с поздней датой не
|
||||
показывает состояния старше. Модельный мир её держит — состояние читается на
|
||||
границе суток, и слепок дня выходит чистой функцией зерна и дня; источник,
|
||||
собирающий слепок из отставшей реплики, её ломает. Деловая метка `updated_at`
|
||||
при этом монотонна по слепкам, поэтому выбор по дате слепка даёт тот же ответ,
|
||||
что выбор по метке (замер — «Что проверено»). Тихая правка строки — ручным
|
||||
`UPDATE` мимо приложения, миграцией — метку не двигает, но следующий слепок
|
||||
перекроет её сам, пока заказ в окне изменяемости. За окном источник заказ
|
||||
больше не показывает, и там помогает только сверка самих полей, в терминах
|
||||
dbt snapshots — стратегия `check` вместо `timestamp`.
|
||||
`ods.order_v` читает `ods.order_dist FINAL` и возвращает одну текущую строку
|
||||
на `order_id`, даже если фоновые слияния еще не убрали старые версии.
|
||||
Без `FINAL` физическая таблица может показать несколько строк заказа.
|
||||
Историю ODS не гарантирует: старые версии постепенно удаляются при слияниях.
|
||||
Отсутствие заказа в новом слепке не удаляет его из ODS.
|
||||
|
||||
Обычное чтение `_dist` показывает физически сохранившиеся версии и нужно для
|
||||
диагностики. Их число зависит от фоновых слияний: ODS не служит архивом истории.
|
||||
`ods.order_v` сохраняет те же источник-ориентированные поля без обогащения
|
||||
данными модели, но возвращает одну актуальную версию на `order_id`. Сначала оно
|
||||
может быть простым представлением над `_dist FINAL`; способ выбора можно
|
||||
заменить, не меняя потребителей.
|
||||
|
||||
Это представление остаётся ответственностью ODS: оно скрывает механику чтения
|
||||
версий, но не строит бизнес-модель. DDS читает `ods.order_v` и отдельно решает,
|
||||
какие сущности, связи и производные признаки ему нужны. Судьба служебных
|
||||
колонок выше ODS решена вместе с моделью DDS: чужие ярлыки туда не переезжают
|
||||
([модель заказа](../dds/order.md)).
|
||||
DDS читает `ods.order_v`, переводит времена в пояс счетчика и разбирает
|
||||
позиции. Для своей загрузки DDS записывает собственные служебные метки —
|
||||
[модель заказа](../dds/order.md#служебные-колонки).
|
||||
|
||||
## Одно чтение Kafka
|
||||
|
||||
Стандартный слепок содержит около полутора тысяч строк, тогда как предел одной
|
||||
порции на стенде — десятки тысяч сообщений. Поэтому один запуск Airflow делает
|
||||
один прямой `SELECT`, без цикла до пустоты и без фиксации конечных офсетов.
|
||||
Один запуск делает один прямой `SELECT`, без чтения в цикле до пустоты.
|
||||
Обычный слепок содержит около полутора тысяч строк, а предел порции
|
||||
на стенде — десятки тысяч сообщений. Это допущение о размере данных:
|
||||
Kafka не сообщает запросу, что прочитан весь слепок.
|
||||
|
||||
Это допущение о размере стенда, а не доказательство полноты слепка. Если Kafka
|
||||
вернёт короткую порцию, непрочитанный хвост останется в топике и приедет в один
|
||||
из следующих запусков. После отказа от замены партиции это задержка, а не потеря
|
||||
или публикация неполного дня.
|
||||
Короткая порция оставляет непрочитанные сообщения для следующих запусков.
|
||||
Поэтому ODS добавляет версии заказов по мере поступления. Замена партиции
|
||||
по `snapshot_date` могла бы опубликовать неполный набор заказов
|
||||
([ADR 0010](../../adr/0010-order-versions-in-ods.md)).
|
||||
|
||||
Отказ — случай другой, и «хвост дождётся» на него не распространяется. Офсеты
|
||||
порции коммитятся в момент чтения, поэтому упавшая вставка уносит прочитанное с
|
||||
собой: повторное чтение вернёт ноль, а часть строк может уже лежать на шарде.
|
||||
Позиция мира при этом не двигается, и тот же день уедет заново — в STG он
|
||||
окажется частичным дублем, а старый хвост, ушедший той же порцией, не вернётся
|
||||
([ADR 0008](../../adr/0008-order-ingestion.md), «Следствия»).
|
||||
|
||||
## Отклонённые варианты
|
||||
|
||||
- Партиционная идемпотентность — `REPLACE PARTITION snapshot_date` или
|
||||
`ReplacingMergeTree` по `(snapshot_date, order_id)`: у потребителя нет
|
||||
признака полноты партиции, а дата наблюдения становится частью ключа
|
||||
сущности.
|
||||
- Обычный `MergeTree` в ODS с дедупликацией только в DDS: навсегда сохраняет
|
||||
технические повторы там, где семантика версии уже известна.
|
||||
- Маркер, опись, чтение до пустоты или конечные офсеты: добавляют протокол ради
|
||||
объёма, который с большим запасом помещается в одну порцию.
|
||||
- Два `task_id` или материализованный классификатор: дробят один короткий
|
||||
переход слоя, не добавляя транзакционности.
|
||||
- Представление текущего состояния в DDS и готовая схема `dds.order` в этой
|
||||
задаче: перекладывают механику ODS на следующий слой и преждевременно задают
|
||||
модель данных.
|
||||
|
||||
## Риски и проверка
|
||||
|
||||
- На малой управляемой порции дать по одной строке каждого класса брака и две
|
||||
годные версии одного `order_id`. Две цели должны сохранить все непустые
|
||||
сообщения, а `ods.order_v` — вернуть новую версию независимо от фонового
|
||||
слияния.
|
||||
- Повторить переход с тем же `_load_id`: строка в `ods.order_v` и её `_load_ts`
|
||||
не должны измениться; версии одного заказа должны остаться на одном шарде и
|
||||
в одной партиции. Таблица ошибок может снова записать тот же брак: совпавшие
|
||||
`_load_id` и Kafka-координаты показывают повтор задачи.
|
||||
Сбой после чтения опаснее короткой порции. Чтец подтверждает позиции
|
||||
сообщений в Kafka — офсеты — при прямом чтении. Если последующая вставка
|
||||
в STG упадет, эти сообщения уже не будут прочитаны той же группой повторно;
|
||||
часть из них может успеть записаться на шарды. Позиция модельного времени
|
||||
при неуспехе не продвигается. Повтор шагового дага снова отправит слепок
|
||||
того же дня, но не восстановит старые сообщения других слепков,
|
||||
если они попали в неудачную порцию.
|
||||
Граница восстановления описана в [ADR 0008](../../adr/0008-order-ingestion.md#следствия).
|
||||
|
||||
## Что проверено
|
||||
|
||||
Переход STG → ODS снят на живом стенде 18 августа 2026 года при исполнении #94.
|
||||
Опыты этого раздела прогнаны и подтвердили обещанное: разбиение строк STG на
|
||||
годные строки и брак полное и непересекающееся; две версии одного заказа легли в
|
||||
одну партицию и на один шард, а `ods.order_v` вернуло позднюю независимо от
|
||||
фонового слияния; повтор задачи с тем же `_load_id` строку в `ods.order_v` и её
|
||||
`_load_ts` не изменил, а таблица ошибок записала тот же брак второй раз; строки
|
||||
опытов убраны. Числа, механика опытов и поведение ClickHouse, на которое всё это
|
||||
опирается, — [документ хранилища](../storage.md), «Что проверено».
|
||||
Это результаты прежних опытов на стенде; при редактуре они заново
|
||||
не выполнялись.
|
||||
|
||||
После исправления
|
||||
[#102](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/102)
|
||||
честный прогон дня снят на живом стенде 20 августа 2026 года: все 1694 строки
|
||||
слепка дня 7 приняты, `ods.order_errors` осталась пустой. У заказа
|
||||
`20260608-0007`, который прежде приходил с отрицательным итогом, теперь
|
||||
`5490.00 − 823.50 + 299.00 = 4965.50`.
|
||||
- 18 августа 2026 года (#93, #94) прямое чтение приняло слепок,
|
||||
а разбор разделил строки на годные и ошибки без потерь и пересечений.
|
||||
Две версии заказа попали на один шард в одну партицию; `ods.order_v`
|
||||
вернул позднюю. Повтор разбора с тем же `_load_id` сохранил текущую строку
|
||||
и ее `_load_ts`, но повторил ошибки. Подробности —
|
||||
[справочник хранилища](../storage.md#что-проверено) и
|
||||
[ADR 0008](../../adr/0008-order-ingestion.md#что-проверено).
|
||||
- 20 августа после исправления
|
||||
[#102](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/102)
|
||||
приняты все 1694 строки слепка дня 7 без ошибок. Итог заказа
|
||||
`20260608-0007`: `5490.00 − 823.50 + 299.00 = 4965.50`.
|
||||
- 21 августа (#124) пересборка через `SQLExecuteQueryOperator` завершила
|
||||
`pull_batch` и `parse_batch`; в `system.query_log` осталось по одному
|
||||
завершенному запросу `etl` на забор, годные строки и ошибки.
|
||||
- 22 августа (#137) во всех 3034 парах с одинаковым `updated_at`
|
||||
деловые поля совпали побайтово; по слепкам время изменения не убывало.
|
||||
- 26 августа (#165) после двух слепков физическая таблица содержала
|
||||
3356 версий 1917 заказов. В `ods.order_v` у всех заказов была дата
|
||||
последнего слепка, а деловые поля совпали с `argMax` по `updated_at`
|
||||
из сырых данных.
|
||||
|
||||
При исполнении #124 21 августа 2026 года адресная пересборка мира прошла через
|
||||
`SQLExecuteQueryOperator`: `pull_batch` и `parse_batch` завершились успешно. В
|
||||
`system.query_log` на первой ноде осталось ровно по одному завершённому запросу
|
||||
пользователя `etl` для забора в STG, записи годных строк и записи брака.
|
||||
|
||||
Дисциплина источника замерена 22 августа 2026 года при исполнении #137.
|
||||
Во всех 3034 парах строк с равным `updated_at` деловые поля совпали
|
||||
побайтово, а по слепкам метка ни разу не убывала.
|
||||
|
||||
Текущее состояние проверено на пересобранном мире 26 августа 2026 года при
|
||||
исполнении #165. После двух слепков физическая таблица содержала 3356 версий
|
||||
1917 заказов. У всех строк `ods.order_v` дата слепка совпала с последним слепком
|
||||
заказа; деловые поля совпали с эталоном `argMax` по `updated_at` из сырых
|
||||
данных. В обеих сверках расхождений нет.
|
||||
|
||||
Забор из Kafka в STG снят на живом стенде 18 августа 2026 года при исполнении
|
||||
#93: одно прямое чтение приносит весь слепок дня, метаданные доставки доступны,
|
||||
офсеты коммитятся, а сбой забора не двигает позицию мира. Числа — [ADR
|
||||
0008](../../adr/0008-order-ingestion.md), раздел «Что проверено».
|
||||
|
||||
MCP Context7 в сессии проектирования был недоступен. На локальном ClickHouse
|
||||
`26.3.17.56` проверено, что прямой `SELECT` Kafka Engine завершается после
|
||||
одной порции, а `FINAL` через `Distributed` исполняется на таблицах шардов.
|
||||
Поэтому версии одного `order_id` направляются на один шард. Фоновое схлопывание
|
||||
`ReplacingMergeTree` и необходимость точного чтения сверены с
|
||||
[официальной документацией](https://clickhouse.com/docs/reference/engines/table-engines/mergetree-family/replacingmergetree),
|
||||
поведение чтения — с исходниками той же версии
|
||||
На ClickHouse `26.3.17.56` отдельно проверены одна порция при прямом
|
||||
`SELECT` Kafka и выполнение `FINAL` на шардах через Distributed.
|
||||
При проектировании Context7 был недоступен; чтение сверяли с исходниками
|
||||
той же версии:
|
||||
[`StorageKafka.cpp`](https://github.com/ClickHouse/ClickHouse/blob/v26.3.17.56-lts/src/Storages/Kafka/StorageKafka.cpp) и
|
||||
[`KafkaSource.cpp`](https://github.com/ClickHouse/ClickHouse/blob/v26.3.17.56-lts/src/Storages/Kafka/KafkaSource.cpp).
|
||||
Повторная сверка правил версий с документацией — в
|
||||
[справочнике хранилища](../storage.md#актуальные-события).
|
||||
|
||||
## Связанные решения
|
||||
|
||||
|
||||
Reference in new issue
Block a user