docs(ods): находки ревью — опыт с _load_ts, точность формулировок, рез повторов

Зачем: холодное ревью по двум линиям нашло дыру в следе опытов и три места,
где текст утверждает не то, что построено.

Что:
- Опыт «_load_ts переносится из сырья» прогнан и записан: у двух тысяч
  событий метка совпала с меткой одной из доставок, случаев «метки нет среди
  доставок» ноль. Туда же — ответ про форму ключа ODS: вопрос раздела 11
  спеки закрывался молча.
- Дока хранилища говорила, что предикат собран из функций, не возвращающих
  NULL; построено иначе — обнуляемый разбор есть, но кончается IS NOT NULL.
- Записана гарантия на JSONType: на не-JSON и пустой строке она отдаёт Null и
  не бросает, то есть годится в предикат. Раньше первый класс брака стоял на
  замере соседней функции.
- ttl_only_drop_parts у таблицы ошибок назван в доке хранилища.
- Комментарий матвью ужат: три вопроса строгого приёма пересказывали ADR 0005
  целиком. Осталось то, чего по коду не видно, — запрет трогать arraySort и
  замер про ISO-8601. Убрано неверное «в полусотне строк» и упоминание имени
  таблицы хранилища в докстринге контракта генератора.

Проверка: DDL применяется на живом кластере; make lint, typecheck, docs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 16:16:09 +03:00
co-authored by Claude Opus 5
parent 6910440400
commit 68f789ba91
5 changed files with 36 additions and 38 deletions
+4 -1
View File
@@ -191,7 +191,10 @@ ClickHouse 26.3.17.56. Все четыре ответили так, как жд
класс брака поэтому проверяет именно объект, а не валидность;
- `JSONExtractKeys('123')` возвращает пустой массив, и он же приходит от
вовсе не-JSON. Значит скаляр проваливает и сверку ключей — отсюда
обязательный порядок классов;
обязательный порядок классов. Раз `isValidJSON` объект от скаляра не
отличает, первый класс держит `JSONType`: она возвращает `Object` у объекта,
`Int64` у скаляра `123` и `Null` у вовсе не-JSON и у пустой строки —
исключения не бросает ни в одном случае, то есть годится в предикат;
- `JSONAsString` на некорректном вводе падает, а не пропускает строку: код 117
`INCORRECT_DATA`, «JSON object must begin with '{'». Падает и на скаляре
`123`. На этом стоит отказ от него в пользу `RawBLOB`;
+19 -5
View File
@@ -219,9 +219,11 @@ Airflow она стоит — там это обычный `SETTINGS` у зап
нахлёста.** Одна забирает годные строки в `ods.event_dist`, вторая — брак в
`ods.event_errors_dist`. Строка, подошедшая обеим, задвоится; не подошедшая ни
одной — исчезнет молча. Держится это формой: второе условие пишется буквальным
отрицанием первого, а сам предикат собирается только из функций, не возвращающих
NULL, — иначе трёхзначная логика даст строку, которую не возьмёт ни `условие`,
ни `NOT условие`. Те же функции не должны и бросать исключений: упавшая матвью
отрицанием первого, а сам предикат NULL не возвращает ни в одной своей части, —
иначе трёхзначная логика даст строку, которую не возьмёт ни `условие`, ни
`NOT условие`. Обнуляемый разбор в предикате поэтому есть, но заканчивается
`IS NOT NULL`, а сравнения дают 0 или 1. Функции предиката не должны и бросать
исключений: упавшая матвью
роняет вставку и останавливает потребление до починки
([ADR 0005](../adr/0005-event-ingestion.md)).
@@ -283,7 +285,10 @@ D0 и к реальному календарю не привязана; паке
брака нет разобранных полей: шардируется `cityHash64` сырой строки — `ClientID` у
строки, которая не разобралась, взять неоткуда; нарезается по дню загрузки, как и
сырьё; живёт месяц. Дольше сырья — намеренно: если брак истекает вместе с ним,
разбираться к моменту разбирательства будет уже нечем.
разбираться к моменту разбирательства будет уже нечем. Снятие — целыми кусками
(`ttl_only_drop_parts`), как у сырья и по той же причине: строки в партиции дня
загрузки разного возраста не более чем на сутки, и куску незачем переживать
срок из-за самой свежей строки.
Класс брака лежит в колонке `error_class` типа `LowCardinality(String)`. Без неё
в таблице копятся строки «что-то не так» без ответа на «что именно», а витрине
@@ -386,7 +391,7 @@ ODS. Второе: матвью приёма создаётся последне
таймауту; `CREATE ... IF NOT EXISTS` на существующем объекте не бросает.
**Проверено на стенде.** Опыты прогнаны на живом кластере: пять при исполнении
#37 (четыре 5 августа 2026 года, пятый 6 августа) и два при исполнении #43
#37 (четыре 5 августа 2026 года, пятый 6 августа) и четыре при исполнении #43
(7 августа). Все подтвердили то, что здесь написано.
- Матвью с источником-`Distributed` срабатывает на вставку именно в эту
@@ -403,6 +408,15 @@ ODS. Второе: матвью приёма создаётся последне
`Distributed` кладёт блок в спул и сразу возвращает управление, так что на
сносе локальной ошибка всплыла бы фоном и утверждение показалось бы
опровергнутым.
- `_load_ts` в `ods.event` — это метка исходной строки сырья, а не время
разбора. Сверено по `WatchID` на двух тысячах событий модельного дня,
залитого дважды: у каждого события метка совпала с меткой одной из двух его
доставок, а после `FINAL` — с меткой поздней. Случаев «метки нет среди
доставок» ноль, то есть `now64()` в матвью разбора нет.
- Форма ключа `ods.event` принимается такой, как её задумала спека: выражение
`intHash32(ClientID)` стоит в ключе сортировки `ReplacingMergeTree`, а
`SAMPLE BY` — по тому же выражению. Вопрос стоял открытым в разделе 11
мастер-спеки; ответ — DDL применяется и таблица работает.
- `RawBLOB` даёт ровно одну строку на каждое непустое сообщение. Три сообщения
с ключами, поставленные в очередь до одного сброса продюсера, стали тремя
+4 -4
View File
@@ -585,10 +585,10 @@ v2 стартует пустым, поэтому объём ниже — это
Список убывает по мере постройки: проверенное уходит отсюда, а ответ с датой
остаётся там, где на него опираются. Формат чтеца и форма виртуальной метки
времени закрыты при исполнении #37, форма ключа ODS, поведение матвью над
`Distributed` и запасной именованный кортеж — при исполнении #43; ответы в
[доке хранилища](../architecture/storage.md) и
[ADR 0005](../adr/0005-event-ingestion.md), разделы «Что проверено».
времени закрыты при исполнении #37; форма ключа ODS и поведение матвью над
`Distributed` — при исполнении #43, ответы в [доке
хранилища](../architecture/storage.md), раздел «Что проверено»; запасной
именованный кортеж — там же в [ADR 0005](../adr/0005-event-ingestion.md).
- Поведение соединения двух Distributed-таблиц и `distributed_product_mode`
эмпирически на стенде (хвост #14).