diff --git a/docs/architecture/orders/ingestion.md b/docs/architecture/orders/ingestion.md index 321d8a5..39b9e5a 100644 --- a/docs/architecture/orders/ingestion.md +++ b/docs/architecture/orders/ingestion.md @@ -106,6 +106,20 @@ JSON-объектом с точным набором ключей: `order_id`, ` - `_load_id` — какой запуск Airflow принял строку; - `_load_ts` — когда строка приехала в хранилище. +Версию выбирает только `updated_at`. Заказ, не менявшийся несколько дней, +приезжает в нескольких слепках одинаковыми деловыми полями, и какая из этих +строк переживёт схлопывание — не обещано. Поэтому `snapshot_date` выжившей +строки — один из дней, когда источник показывал эту версию, не обязательно +последний; то же с `_load_id` и `_load_ts`. Вопрос «когда заказ наблюдался в +последний раз» задаётся сырью STG, пока то живо, — а не одной строке ODS. + +Сама схема версий стоит на допущении об источнике: уточнение заказа поднимает +`updated_at`. Генератор моделирует источник с триггерной меткой и допущение +выполняет (замер — «Что проверено»); источник, умеющий уточнить строку тихо — +ручным `UPDATE` мимо приложения, миграцией, — ломает его, и тогда версии по +метке времени мало: нужна сверка самих полей, в терминах dbt snapshots — +стратегия `check` вместо `timestamp`. + Обычное чтение `_dist` показывает физически сохранившиеся версии и нужно для диагностики. Их число зависит от фоновых слияний: ODS не служит архивом истории. `ods.order_v` сохраняет те же источник-ориентированные поля без обогащения @@ -186,6 +200,13 @@ JSON-объектом с точным набором ключей: `order_id`, ` `system.query_log` на первой ноде осталось ровно по одному завершённому запросу пользователя `etl` для забора в STG, записи годных строк и записи брака. +Допущение и произвол ярлыков замерены 22 августа 2026 года при исполнении +#137: во всех 3034 парах строк с равным `updated_at` деловые поля совпали +побайтово, по слепкам метка ни разу не убывала, а `ods.order_v` сошёлся +построчно с эталоном `argMax` по `updated_at`, посчитанным прямо из сырья. +Произвол ярлыков виден въявь: у 86 заказов стартового мира выжила строка +серединного слепка — не первого и не последнего. + Забор из Kafka в STG снят на живом стенде 18 августа 2026 года при исполнении #93: одно прямое чтение приносит весь слепок дня, метаданные доставки доступны, офсеты коммитятся, а сбой забора не двигает позицию мира. Числа — [ADR diff --git a/docs/architecture/storage.md b/docs/architecture/storage.md index d88b19c..1ab42e9 100644 --- a/docs/architecture/storage.md +++ b/docs/architecture/storage.md @@ -84,6 +84,18 @@ CSV-файла. Живёт он вне цепочки слоёв, в зоне `d Пошардовые счётчики STG и ODS поэтому не сходятся и сходиться не должны — сверять слои можно только через `_dist`. +Разные ключи у слоёв ставят капкан на вставке `INSERT INTO цель_dist SELECT … +FROM источник_dist`. Заводское значение `parallel_distributed_insert_select` — +2: если обе таблицы на одном кластере, вставка исполняется локально на каждом +шарде, минуя ключ шардирования цели. Строки наследуют раскладку источника, и +ошибки при этом нет — расхождение видно только пересчётом ключей по +`hostName()`. Так половина заказов и легла на оба шарда, где +`ReplacingMergeTree` через границу шарда дубли не схлопывает (замер — «Что +проверено», #137). Лекарство — явное `parallel_distributed_insert_select = 0` +в `SETTINGS` такой вставки: раскладку делает инициатор, по ключу цели +(`sql/ods/order_parse.sql`). Капкан ждёт и сборки DDS: они тоже будут читать +`_dist` и писать в `_dist` с другим ключом. + Ключи ко-локации названы заранее, потому что на них стоит политика соединений из раздела 6 спеки: обычное соединение разрешено только по ключу ко-локации, всё прочее — через `GLOBAL`. Значит `dds.session` и `dds.identity_map` шардируются по @@ -573,6 +585,15 @@ DDL-словарь с файловым источником внутри `user_f разобрано, почему отказ в праве на движок называет не то право, которого не хватает. +**Проверка ко-локации заказов 22 августа 2026 года (#137).** Context7 и +`system.settings` закреплённого ClickHouse 26.3 назвали одну и ту же тройку +значений `parallel_distributed_insert_select`: `0` — выключено, `1` — `SELECT` +на каждом шарде, `2` — `SELECT` и `INSERT` на каждом шарде; заводское — `2`. +На живых данных видны и дефект, и починка: до правки 1189 из 1662 заказов +лежали на обоих шардах, после явного нуля и повторного разбора того же +`_load_id` — ни одного. Вершина версий уцелела: деловые поля `ods.order_v` +сошлись с эталоном, посчитанным прямо из сырья. + **Проверка версий заказов 18 августа 2026 года (#94).** MCP Context7 подтвердил, что `JSONType` возвращает имя типа значения JSON, — на нём стоит проверка типов в предикате приёма заказов. Остальное снято на закреплённом ClickHouse 26.3. diff --git a/sql/ddl/20-ods-tables.sql b/sql/ddl/20-ods-tables.sql index 3e56a52..35070c4 100644 --- a/sql/ddl/20-ods-tables.sql +++ b/sql/ddl/20-ods-tables.sql @@ -163,7 +163,10 @@ ENGINE = Distributed('clickstream_cluster', 'ods', 'event_errors_rep', cityHash6 -- updated_at — какая бизнес-версия новее; snapshot_date — в слепке какого -- модельного дня источник показал строку; _load_id — какой запуск Airflow её -- принял; _load_ts — когда она приехала в хранилище. Ключом сущности не --- становится ни одна из трёх последних: они про наблюдение и загрузку. +-- становится ни одна из трёх последних: они про наблюдение и загрузку. Версию +-- выбирает только updated_at, поэтому у выжившей после схлопывания строки +-- snapshot_date — один из дней, когда источник показывал эту версию, не +-- обязательно последний (docs/architecture/orders/ingestion.md). -- -- PARTITION BY toDate(created_at) — по дню создания строки в источнике. Он -- неизменен у всех версий заказа, поэтому версии лежат в одной партиции и diff --git a/sql/ods/order_parse.sql b/sql/ods/order_parse.sql index 0600773..2b757f1 100644 --- a/sql/ods/order_parse.sql +++ b/sql/ods/order_parse.sql @@ -92,7 +92,13 @@ SELECT _load_ts FROM stg.orders_raw_dist WHERE _load_id = {load_id:String} AND row_is_valid -SETTINGS distributed_foreground_insert = 1; +-- Заводская двойка parallel_distributed_insert_select исполняет вставку из +-- Distributed в Distributed локально на каждом шарде, минуя ключ шардирования +-- цели, — так заказы и расползлись по обоим шардам. Ноль отдаёт раскладку +-- инициатору, по ключу цели. Капкан целиком — docs/architecture/storage.md, +-- «Раскладка по шардам». +SETTINGS distributed_foreground_insert = 1, + parallel_distributed_insert_select = 0; -- Брак: тот же срез и буквальное отрицание того же предиката. -- @@ -118,4 +124,7 @@ SELECT _load_ts FROM stg.orders_raw_dist WHERE _load_id = {load_id:String} AND NOT row_is_valid -SETTINGS distributed_foreground_insert = 1; +-- Тот же ноль: ключ брака сегодня совпадает с ключом сырья, и локальная +-- запись легла бы верно — но по совпадению, а не по контракту. +SETTINGS distributed_foreground_insert = 1, + parallel_distributed_insert_select = 0;