- Зачем: - верхние смещения можно получить тем же административным клиентом без одноразового Consumer и фиктивной группы. - Что: - чтение хвоста переведено на AdminClient.list_offsets с OffsetSpec.latest. - назначение system.one названо рядом с запросом, а документация и запись проверок сокращены и переставлены по смыслу. - Проверка: - make config-test; make lint. - живые проверки готового и отсутствующего разделов Kafka; полный etl_pipeline.
416 lines
38 KiB
Markdown
416 lines
38 KiB
Markdown
# Проверки: карта целей
|
||
|
||
Документ отвечает на два вопроса. Первый: что именно утверждает каждая цель
|
||
`make` и сколько стоит её прогон. Второй, ради которого документ и заведён:
|
||
куда положить новую проверку — так, чтобы это решалось по карте, а не чтением
|
||
скриптов.
|
||
|
||
Зона ответственности у документа одна — проверки. Что именно они стерегут,
|
||
описано в других местах: устройство хранилища — в
|
||
[storage.md](storage.md), замысел стенда — в спеке [«Боевой реализм стенда
|
||
(v2)»](../specs/2026-07-30-stand-v2-realism.md).
|
||
|
||
## Ось: кого спрашивают
|
||
|
||
Цели различаются не ценой, а тем, к кому обращён вопрос. ClickHouse отвечает
|
||
сам и за миллисекунды. Airflow отвечает через такт планировщика. Superset —
|
||
через сессию и обход метаданных. Быстрота выходит следствием этого различия, а
|
||
не критерием деления.
|
||
|
||
Совпадение цены и назначения здесь побочное, и это видно по дырам, которые
|
||
каждая цель оставляет соседке. `make smoke` не заметит перепутанных местами
|
||
макросов `shard`: обе ноды здоровы и порты отвечают. `make check-clickhouse` не
|
||
заметит потерянного подключения Superset: он про ClickHouse и только.
|
||
|
||
Отсюда правило для новой проверки: **спроси, кого она спрашивает.** Счётчики
|
||
против описи — вопрос к ClickHouse: строки в `ods.event` считает сам
|
||
сервер и отвечает сразу, значит дом им в `check-clickhouse`, даже если по цене
|
||
они подошли бы смоуку.
|
||
|
||
## Карта целей
|
||
|
||
Цели живут за двумя дверями. В корне — цели стенда; в `generator/` — цели
|
||
генератора: он отдельный пакет со своим `pyproject.toml`, локом и образом, и
|
||
спрашивают его отдельно.
|
||
|
||
Стенд нужен трём целям из восьми. Цена — замер, см. «Что проверено».
|
||
`make config-test`, `make smoke` и `make check-services` перемерены 21 августа
|
||
2026 года, `make check-clickhouse` — 19 августа, корневой `make lint` —
|
||
9 августа, `make test` — 7 августа; остальные стоят с замера 6 августа.
|
||
|
||
| Цель | Откуда | Что утверждает | Стенд | Цена |
|
||
|---|---|---|---|---|
|
||
| `make config-test` | корень | Compose разбирается, файлы DAG и дашборды Grafana синтаксически целы, в diff нет пробельных ошибок. О работоспособности не говорит ничего | не нужен | 1 с |
|
||
| `make lint` | корень | Код стенда — `dags/` и `infra/superset/` — отформатирован и проходит ruff | не нужен | 0,4 с |
|
||
| `make smoke` | корень | Стенд **собран**: службы живы, порты отвечают, подключения настроены друг на друга. Вширь и по касательной к каждой службе. Единственная цель, которая здесь правда смоук | нужен | 17 с |
|
||
| `make check-clickhouse` | корень | Всё, что спрашивают **у ClickHouse** и он отвечает сам: макросы, шарды, реплики, путь в keeper, ключ шардирования, очередь распределённых DDL, ответ словаря товаров, счёт событий стартового мира против описи | нужен | 24 с |
|
||
| `make check-services` | корень | **Службы работают**: DAG запускается и доходит, топик создаётся и удаляется, Superset логинится и ходит в базу | нужен | 93 с |
|
||
| `make lint` | `generator/` | Код генератора отформатирован и проходит ruff | не нужен | 0,4 с |
|
||
| `make typecheck` | `generator/` | Типы генератора сходятся (ty) | не нужен | 0,5 с |
|
||
| `make test` | `generator/` | Генератор делает то, что обещает; схема события остаётся объявленным контрактом, а собранные из кода [описание выгрузки](../formats/clickstream-event.md) и [опись мира](../../data/world-inventory.json) — свежими | не нужен | 71 с |
|
||
|
||
**Строк с именем `make lint` две, и это не опечатка.** Одноимённые цели за
|
||
разными дверями охватывают разное: корневая не видит ни одного файла
|
||
генератора, генераторная — ни одного файла стенда. Человек в корне видит
|
||
зелёное и может решить, что зелёный весь репозиторий.
|
||
|
||
Две тонкости, которых не видно из таблицы:
|
||
|
||
- **Версия ruff у корневой цели закреплена в самом вызове** (`uvx ruff@0.16.1`)
|
||
и равна той, что в `generator/uv.lock`. Равенство держится руками: обновили
|
||
лок — поправьте и вызов, сверять их некому.
|
||
- **Охват держат аргументы вызова, а не устройство дерева.** Python,
|
||
положенный вне двух названных в таблице путей, не проверит ни одна из дверей.
|
||
|
||
**Корневого `typecheck` нет, и это решение, а не пропуск.** Проверке типов мало
|
||
прочитать файл: чтобы понять `from airflow.sdk import dag`, ей нужен
|
||
установленный Airflow, а он живёт в образе, не на машине — 132 пакета ради двух
|
||
файлов пробников (замер 9 августа 2026 года, `uv pip compile`). Цена решения
|
||
называется честно: две настоящие находки `ty` в `dags/test_kafka.py` постоянного
|
||
сторожа не получают. Вопрос вернётся на этапе 5, когда придут настоящие даги:
|
||
тогда окружение окупится, и условие возврата — именно это, а не «стало
|
||
неудобно».
|
||
|
||
Цена самого `make up` с пустых томов — 8 м 18 с до переезда жизненного цикла в
|
||
Airflow. В цену целей она не входит. На готовом мире подъём больше не
|
||
пересчитывает данные: `world_initialize` зеленеет без изменений.
|
||
|
||
`make rebuild-storage` тоже не проверка. Команда через Airflow удаляет
|
||
прикладные базы ClickHouse, топики и позиции групп Kafka, заново применяет DDL,
|
||
заливает стартовый мир и возвращает его пульт к концу стартовых дней. Службы и
|
||
их тома она сохраняет, поэтому возврат к каноническим данным не требует
|
||
холодного запуска всего стенда.
|
||
|
||
## Правило: смоук обязан оставаться быстрым
|
||
|
||
`make smoke` — быстрая проверка для регулярного прогона: её гоняют не
|
||
задумываясь, и потому она обязана укладываться в секунды. Тяжёлые проверки в
|
||
неё включать не следует: тяжёлая — та, из-за которой смоук перестаёт быть
|
||
быстрым. Поодиночке это единицы и десятки секунд, вместе — минуты. Такие
|
||
проверки живут в `make check-services`.
|
||
|
||
На практике дорого обходится не работа, а ожидание службы: такт планировщика
|
||
Airflow, сессия Superset. `make check-clickhouse` создаёт таблицы, вставляет
|
||
строки и гоняет распределённые DDL — и укладывается в семь секунд, потому что
|
||
ClickHouse отвечает сразу.
|
||
|
||
Первый абзац — правило, второй — наблюдение, по которому тяжёлую проверку
|
||
узнают заранее, не замеряя.
|
||
|
||
## Какую проверку когда запускать
|
||
|
||
Деление целей ценно ровно до тех пор, пока оно не превратилось в «гонять
|
||
всегда всё».
|
||
|
||
Состояние перед проверкой восстанавливается в самой узкой достаточной области:
|
||
сначала объект или партиция, затем база слоя. Если нужен целиком чистый
|
||
прикладной мир, `make rebuild-storage` пересобирает его базы, топики, позиции
|
||
потребителей и данные. `make clean` нужен, только когда меняется или сломано
|
||
постоянное состояние вне этих баз либо проверяется сам запуск с пустых томов.
|
||
Требование к обоснованию полного сброса задаёт `AGENTS.md`. Неизвестную поломку
|
||
сначала исследуют: полный сброс уничтожает её следы.
|
||
|
||
| Когда | Что гонять |
|
||
|---|---|
|
||
| Правка в работе | `make config-test` и `make smoke` |
|
||
| PR или задача | плюс цели, которых правка касалась: DAG-и, Superset или Kafka — `check-services`; DDL, кластер или данные — `check-clickhouse` |
|
||
| Приёмка этапа | все три цели на стенде; запуск с пустых томов — только для изменений начальной сборки или постоянного состояния служб |
|
||
|
||
Правки генератора добавляют к этому `make lint`, `make typecheck` и `make test`
|
||
из каталога `generator/`: стенд им не нужен, а `make test` из них самая
|
||
дорогая. Правки дагов и Superset — корневой `make lint`.
|
||
|
||
Тринадцать секунд из её семидесяти одной — пересборка восьми модельных дней
|
||
для сверки с описью мира. Дешевле хеши не сравнить: чтобы узнать, тот ли
|
||
получается мир, его надо получить. Место выбрано по той же оси «кого
|
||
спрашивают» — вопрос обращён к коду генератора, стенд ему не нужен, — и
|
||
краснеет проверка там, где надо: сразу после правки генератора. Не будь её,
|
||
расхождение всплыло бы получасом позже, на поднятом стенде, где выглядит
|
||
поломкой хранилища, а не забытой пересборкой.
|
||
|
||
## Порогов по времени здесь нет
|
||
|
||
Цена в таблице — замеренное число с датой замера, а не назначенный порог.
|
||
Автоматической проверки времени в репозитории нет и заводить её не следует.
|
||
Число, вписанное в проверку, становится законом, которого никто не выбирал:
|
||
[ADR 0004](../adr/0004-resource-limits.md) разбирает ровно этот случай — оценка
|
||
из спеки попала жёстким порогом в `make smoke`, `make smoke` стал критерием
|
||
приёмки каждого этапа, и дальше решения сверялись уже с порогом, а не с
|
||
исходным доводом. Спека генератора формулирует ту же позицию прямо:
|
||
«наблюдаемость без порогов».
|
||
|
||
Смотрит на время человек. `make smoke` и `make check-services` печатают его
|
||
сами — последней строкой `ИТОГ: пройдено N, ошибок M, время T с`. Замер при
|
||
приёмке делается руками и называется в теле PR.
|
||
|
||
## Проверка, которая не умеет краснеть, бесполезна
|
||
|
||
Правило, которое стоит держать в голове, правя любую проверку. Проверка,
|
||
никогда не видевшая своей поломки, доказывает только то, что она умеет печатать
|
||
«ЗЕЛЁНО». Убедиться дешевле всего руками: сломайте то, что она стережёт —
|
||
остановите `prometheus`, удалите служебную таблицу пробника на второй ноде, — и
|
||
посмотрите, покраснеет ли прогон и назовёт ли виновника. Не покраснел —
|
||
проверка не работает, и чинить надо её, а не стенд.
|
||
|
||
## Интеграционная проверка постоянной целью не становится
|
||
|
||
Проверка, которая не спрашивает стенд, а **кормит** его — шлёт сообщения,
|
||
вставляет строки, портит данные нарочно, — постоянной целью `make` не живёт.
|
||
Такую проверку гоняют один раз при исполнении, убеждаются, что механизм
|
||
работает, и оставляют след записью в теле PR. Регулярная жизнь стенда — это
|
||
жизнь с менти, а интеграционный прогон в неё не входит; самое большее, он
|
||
становится темой урока.
|
||
|
||
Довод не про цену прогона, а про данные. Вложенное остаётся: у сырья срок
|
||
жизни трое суток, а ODS хранит всё, и события, которых мир не рождал, лягут в
|
||
лабы менти неотличимо от настоящих — считая конверсию, он молча посчитает и
|
||
их. Хуже явного мусора: явный видно.
|
||
|
||
Отсюда правило для того, что всё-таки вкладывает: **прибираться за собой.**
|
||
Дешевле всего это выходит партицией — опыт играет модельный день за границей
|
||
оси мира, его строки ложатся в собственную партицию `EventDate`, и она
|
||
сносится по окончании. Заодно менти видит партицию как единицу работы — тот
|
||
же механизм, на котором стоит переобработка дня X.
|
||
|
||
Что тогда стережёт цепочку постоянно — проверки на **настоящих** данных, тех,
|
||
что стенд произвёл сам: подневный счёт событий против описи мира ловит и
|
||
сломанный разбор (события уехали в брак — счёт разошёлся), и потерю по дороге,
|
||
ничего при этом не вкладывая. Это единственный постоянный сторож цепочки
|
||
Kafka → STG → ODS, и живёт он в `make check-clickhouse`.
|
||
|
||
Правило, которое такая проверка обязана выдержать и которое стоит держать в
|
||
голове для следующей: **на растущих данных утверждать можно только про
|
||
зафиксированный кусок.** Мир растёт — менти переиграет день, этап 5 добавит
|
||
следующий, — и всякое утверждение про таблицу целиком однажды покраснеет
|
||
законно, то есть впустую. Как это сделано здесь, написано в самом скрипте.
|
||
|
||
## Корректность процессов живёт в дагах DQ, а не в целях `make`
|
||
|
||
Цели `make` отвечают на вопрос «стенд собран и работает». Вопрос «данные в нём
|
||
правильные» — работа отдельных дагов качества данных: они приходят вместе со
|
||
следующими уровнями хранилища и заберут многое из того, что просится проверкой
|
||
сейчас. Поэтому находка «это стоило бы проверять регулярно» сначала проверяется
|
||
вопросом, не туда ли ей: если она про свойства данных — про полноту дня, про
|
||
свежесть слоя, про сходимость витрины с источником, — её дом даг, а не
|
||
`check-clickhouse`.
|
||
|
||
Разница видна и менти, и она содержательная. Цель `make` он зовёт руками,
|
||
когда чинит стенд; даг DQ идёт по расписанию рядом с остальным конвейером,
|
||
пишет историю проверок и разбирается как обычная задача Airflow — то есть учит
|
||
тому, как качество данных устроено в бою.
|
||
|
||
## Устройство скриптов
|
||
|
||
| Цель | Скрипт |
|
||
|---|---|
|
||
| `make config-test` | `scripts/config-test.sh` |
|
||
| `make smoke` | `scripts/stand-smoke.sh` |
|
||
| `make check-services` | `scripts/stand-services.sh` |
|
||
| `make check-clickhouse` | `scripts/check-clickhouse.sh` |
|
||
|
||
Особняком стоит `scripts/run-dag.sh`: это не проверка, а общий REST-переход от
|
||
`make` к Airflow. `make up` запускает через него `world_initialize`, а
|
||
`make rebuild-storage` — `world_recreate`; сценарий ждёт появления дага в API,
|
||
а затем до минуты повторяет только стартовый ответ `Dag with dag_id not found`.
|
||
Прочие ответы и сетевые ошибки он не повторяет: при потерянном ответе запуск мог
|
||
уже создаться. Сценарий печатает идентификаторы дага и прогона и возвращает
|
||
вызывающему его итог. Ожидание доставки событий и запуск `orders_ingest` теперь
|
||
видны задачами `world_initialize`, а не спрятаны в shell.
|
||
|
||
Общее у смоука и `check-services` — счёт проверок, обращение к Compose и две
|
||
проверки — вынесено в `scripts/stand-common.sh`; сам он не запускается.
|
||
Оттуда же приходят два решения, которые видно по счёту прогонов:
|
||
|
||
- **`check_containers_survived` стоит в конце обеих целей.** Убитый за память
|
||
контейнер Docker поднимает сам, и проверка здоровья об этом промолчит
|
||
(ADR 0004). Стенд нагружает `check-services`, а увидеть последствия нужно и
|
||
тому, кто гонял один смоук. Это единственная проверка, которая считается
|
||
дважды: 21 у смоука плюс 7 у `check-services` — это 27 разных проверок.
|
||
- **Зависимости машины считает только смоук.** «На машине есть Docker, curl и
|
||
jq» — вопрос к машине, а не к службам, и на оси он стоит рядом с «стенд
|
||
собран». Для `check-services` это условие запуска: без них он не начнёт
|
||
работу и громко скажет об этом, но ЗЕЛЁНО за это не печатает.
|
||
|
||
Вход в Airflow `check-services` выполняет заново — общих переменных у двух
|
||
скриптов нет. Отдельной проверкой этот вход тоже не считается: то же самое
|
||
утверждает смоук.
|
||
|
||
## Что проверено
|
||
|
||
**Приемка #156 27 августа 2026 года.** `make rebuild-storage` занял 7 минут.
|
||
На чистом прикладном мире `world_initialize` сам довел позицию до `8`, дождался
|
||
`etl_pipeline` и оставил заполненными DDS и обе витрины. После пересборки прошли
|
||
`make smoke` 21/21 и `make check-clickhouse` 10/10. Перед стендовыми сценариями
|
||
прошли `make config-test` и `make lint`.
|
||
|
||
Два прогона `world_next_day` не изменили слои; один ручной `etl_pipeline` без
|
||
параметров догнал оба дня. Полный прогон `world_live_day` занял 27 м 2 с и
|
||
завершился только после успешных DDS и DM.
|
||
|
||
Временная синтаксическая поломка `sql/dm/daily_traffic_rebuild.sql` дала
|
||
красную цепочку: DDS завершился успешно, а `daily_traffic`, DM, `etl_pipeline`
|
||
и `world_live_day` покраснели. После возврата исправного SQL ручной
|
||
`etl_pipeline` без параметров завершился успешно на позиции `12` и восстановил
|
||
слои.
|
||
|
||
Для проверки пакетного барьера `stg.hits_raw_mv` временно отсоединили командой
|
||
`DETACH` на обеих нодах. После `world_next_day` отставание Kafka составило
|
||
46 367 (19 128 + 27 239). Во время ожидания матвью вернули командой `ATTACH`.
|
||
Журнал готовности записал повторные опросы с отставанием 46 367, затем 27 239
|
||
и 0; после этого очередь `ods.event_dist` содержала 0 файлов на обеих нодах,
|
||
и ETL завершился успешно. Проверка `EXISTS` снова дала `1` на обеих нодах,
|
||
позиция равна `13`.
|
||
|
||
После пересборки `make check-services` дважды дал 5/1: только импорт Superset
|
||
не уложился в существующий тайм-аут 30 с. Диагностический пробник импорта с
|
||
ожиданием 90 с прошел 3/0. Финальный штатный повтор прошел 7/7 за 129 с.
|
||
|
||
**Перезамер 21 августа 2026 года при исполнении #124.** Образ Airflow собран с
|
||
`apache-airflow-providers-clickhousedb==1.0.0`,
|
||
`apache-airflow-providers-common-sql==2.0.1` и
|
||
`clickhouse-connect==1.6.0`; `pip check` не нашёл несовместимых зависимостей.
|
||
Адресная пересборка мира через `make rebuild-storage` заняла 3 м 5 с: все шесть
|
||
задач DDL, `pull_batch` и `parse_batch` завершились успешно. После неё прошли
|
||
смоук 21/21 за 17 с, проверка ClickHouse 10/10 и межсервисная проверка 7/7 за
|
||
100 с.
|
||
|
||
Первый `make up` после замены образа попал в короткий рассинхрон запуска
|
||
Airflow: API уже сообщил о здоровье и отдал `world_initialize` по GET, но POST
|
||
нового запуска ещё ответил `Dag with dag_id not found`. Ошибок импорта не было;
|
||
через несколько секунд повторный `make up` завершился успешно и не менял
|
||
готовый мир.
|
||
|
||
**Перезамер 21 августа 2026 года при исполнении #114.** После добавления
|
||
`kafka-exporter` и источника ClickHouse смоук прошёл 21/21 за 16–17 секунд,
|
||
`check-services` — 7/7 за 93 секунды. При остановленном экспортёре смоук дал
|
||
19/2: назвал нездоровый сервис и отсутствие одной из четырёх целей. После
|
||
возврата сервиса прогон снова дал 21/21.
|
||
|
||
После переноса #119 поверх этого состава повторный `make up` сохранил готовый
|
||
мир, смоук прошёл 21/21, проверка ClickHouse — 10/10, межсервисная — 7/7.
|
||
|
||
**Приёмка логики #119 до переноса поверх мониторинга 21 августа 2026 года.**
|
||
`make rebuild-storage` через Airflow прошёл за 3 м 9 с: позиция прошла задачи
|
||
`-1` → `0` → `8`, топики получили новые идентификаторы, группы дочитали новые
|
||
топики без отставания, а ODS принял 401 185 событий без брака и 2 851
|
||
актуальный заказ. Задача `build_generator_image` заняла по 1,5 с в двух
|
||
прогонах.
|
||
Повторный `make up` занял 2 м 41 с вместе со штатным повторным запуском служб
|
||
инициализации; `world_initialize` завершился за один цикл опроса и оставил
|
||
счётчики неизменными. Отдельно проверены красные отказы `world_initialize` при
|
||
`-1` и `world_next_day` при отсутствующей позиции, `0` и `-1`; `lifecycle`
|
||
отказано в чтении `system.users`, но разрешено чтение `system.settings`.
|
||
|
||
Итоговый холодный `make up` из пустых томов занял 9 м 39 с после 25 с на
|
||
`make clean`. Даг создал отсутствующую позицию и закончил на `8`; ODS снова
|
||
получил 401 185 событий без брака и 2 851 заказ. У `hits` два раздела, у
|
||
`orders` один, отставание обеих групп равно нулю. На тогдашнем составе стенда
|
||
прошли смоук 20/20, проверка ClickHouse 10/10 и межсервисная проверка 7/7.
|
||
|
||
Оборванную очистку проверили после записи `world_position=-1`: остановили
|
||
первую ноду ClickHouse, и `world_recreate` покраснел на удалении баз. Следующий
|
||
`make up` тоже покраснел и оставил `-1`. После возврата ноды повторный
|
||
`make rebuild-storage` восстановил мир.
|
||
|
||
**Прототип 21 августа 2026 года при исследовании #110.** Предшествующая
|
||
shell-версия узкой пересборки за 2 м 25 с пересоздала прикладные базы, отправила
|
||
401 185 событий и 6 656 версий заказов и вернула позицию с 11 на 8. Замер
|
||
оставлен как ориентир цены, но не подтверждает нынешнюю реализацию Airflow: тот
|
||
вариант не очищал Kafka и был отклонён как второй оркестратор мира.
|
||
|
||
**Перезамер 20 августа 2026 года при исполнении #96, до переезда в Airflow.**
|
||
`make up` после `make clean` прошёл за 8 м 18 с, повтор на живом стенде — за
|
||
3 м 35 с. Отправка семи слепков заняла около 28 секунд.
|
||
|
||
Прежний замер ниже — 2 м 50 с и 1 м 5 с до появления приёма заказов и пульта
|
||
мира. Весь рост приписывать слепкам нельзя: на холодном прогоне основное время
|
||
ушло ещё до сценариев ожидания: `airflow-init` завершился примерно на 320-й
|
||
секунде, обработчик DAG стал здоров примерно на 458-й. Числа здесь называют
|
||
цену нынешнего стенда, а не выносят вердикт о регрессии.
|
||
|
||
**Перезамер 19 августа 2026 года при исполнении #95.** После добавления десятой
|
||
проверки словаря `make check-clickhouse` на чисто поднятом стенде прошёл за
|
||
24 секунды. Это цена всей цели, а не одной новой строки.
|
||
|
||
**Замер 9 августа 2026 года при появлении корневого `lint` (#65).** Три прогона
|
||
подряд — 0,39, 0,36 и 0,39 секунды на трёх файлах стенда; в таблице 0,4 с. На
|
||
машине с непрогретым кешем первый прогон дороже: `uvx` сначала скачивает ruff.
|
||
|
||
Тогда же снято, что двери не перекрываются: `ruff check --show-files` из корня
|
||
перечисляет ровно три файла стенда, из `generator/` — только файлы генератора.
|
||
Корневой конфиг правила генератору не подменяет: конфиг ищется вверх по дереву
|
||
от файла, и ближний выигрывает.
|
||
|
||
**Перезамер 7 августа 2026 года при исполнении #42.** Подъём стенда с нуля
|
||
(`make clean && make up`) — 2 м 50 с, из них 22,5 с занимает сама заливка:
|
||
18,7 с генерация восьми дней и 3,8 с доставка 401 185 сообщений в Kafka.
|
||
Повторный `make up` на живом стенде — 1 м 5 с: мир заливается заново, и это
|
||
не оплошность — `WatchID` те же, повтор схлопнет `ReplacingMergeTree`.
|
||
Смоук — 9 с, `check-clickhouse` — 8 с с новой девятой проверкой. `make test`
|
||
вырос до 71 с: 58 с прежних тестов плюс 13 с на пересборку восьми дней для
|
||
сверки с описью. Прежние 40 с в таблице устарели ещё до #42 — тесты добавляли
|
||
#41 и #43.
|
||
|
||
Что новая проверка **умеет краснеть**, снято двумя поломками того же дня, и
|
||
проверялись обе ветви её диагноза. Снесли партицию `2026-06-03` в
|
||
`ods.event_rep` — проверка покраснела, показала недостающий день и назвала
|
||
адрес: «таблица брака пуста, события не доехали до ODS, начните с чтеца
|
||
топика». Затем положили в сырьё заведомо негодную строку — брак появился, и
|
||
диагноз сменился на второй, с признаком для различения: «сойдётся недостача с
|
||
числом брака — сломан разбор, не сойдётся — брак от прежних опытов». Строки
|
||
опыта убраны, день переигран `make generate-batch GENERATOR_DAY=2`; счёт
|
||
вернулся к 401 185, и это заодно показало дедупликацию: повтор дня не удвоил
|
||
счёт под `FINAL`.
|
||
|
||
Замеры 6 августа 2026 года, стенд поднят заранее. Время взято по `time` и
|
||
совпадает с тем, что цель печатает сама. Оно плавает от прогона к прогону:
|
||
смоук дал 8 секунд дважды (без проверки Kafka, до её появления, — 5 и 6),
|
||
`check-services` — 43 и 44. В таблице стоит большее из замеренных.
|
||
|
||
Перемер 7 августа 2026 года — приёмка этапа 2 целиком: `make clean` и `make up`
|
||
подряд заняли 2 м 58 с и залили стартовый мир, дальше по порядку все три цели
|
||
на стенде и три без него. Смоук дал 9 секунд, `check-clickhouse` — 8,
|
||
`make test` — 71. `check-services` — 59 и 48; в таблицу по прежнему правилу
|
||
пошло большее. Против 44 секунд накануне цель подорожала, и причину назвать
|
||
нечем: между замерами и стартовый мир в хранилище, и холодный подъём перед
|
||
первым прогоном. Число здесь — замер с датой, а не бюджет, и повод разбираться
|
||
появится, когда за него кто-нибудь начнёт держаться.
|
||
|
||
До деления `scripts/stand-smoke.sh` шёл 48 секунд на 25 проверок, из них
|
||
42 секунды съедали шесть: Kafka с машины, два запуска пробников Airflow и три
|
||
проверки Superset. После деления те же 25 проверок разошлись по двум целям:
|
||
19 в смоуке и 6 в `check-services`. Содержание ни одной из них не менялось.
|
||
|
||
Двадцатая проверка смоука — единственная новая: Kafka спрашивают с машины через
|
||
отображённый порт. Деление оставило дыру, которой раньше не было. Про Airflow,
|
||
Superset, Prometheus и Grafana смоук стучится с машины в отображённый порт, а
|
||
про Kafka после переезда знал только «контейнер здоров» — а это вердикт
|
||
проверки состояния из `compose.yaml`, и та спрашивает брокер изнутри по
|
||
внутреннему слушателю. Внешняя дверь оставалась непроверенной до
|
||
`check-services` с его минутой, хотя генератор пишет в Kafka именно с
|
||
машины. Стоит проверка 2,6 секунды, и почти всё это — старт JVM в разовом
|
||
контейнере; ожидание ответа ограничено пятнадцатью секундами, впятеро больше
|
||
замеренного.
|
||
|
||
Что обе разделённые цели умеют краснеть, проверено руками в тот же день. Со
|
||
снятым `prometheus` смоук дал три ошибки и ненулевой код возврата. С
|
||
подменённым ожидаемым UUID подключения Superset так же покраснел
|
||
`check-services`. Новую проверку Kafka проверили её собственной поломкой:
|
||
брокеру объявили адрес `kafka-nowhere:29092`, оставив внутренний слушатель
|
||
целым, — проверка состояния контейнера осталась зелёной, а смоук покраснел
|
||
именно на этой строке. Краснеют они и когда на машине не хватает команды из
|
||
списка зависимостей.
|
||
|
||
Семантика счётчиков Docker `OOMKilled` и `RestartCount`, на которой держится
|
||
`check_containers_survived`, снята отдельными контейнерами и записана в
|
||
ADR 0004, раздел «Что проверено».
|
||
|
||
Разбор Bash из `make config-test` срезан 6 августа 2026 года, и вместе с ним —
|
||
двенадцать строк обхода репозитория со списком файлов. Проверка была сломана
|
||
с рождения: `bash -n` со списком файлов разбирает только первый, остальные
|
||
уходят ему в аргументы, — то есть из пяти скриптов проверялся один, и за всё
|
||
время цели этого никто не заметил. Заводить её заново незачем: скрипты стенда
|
||
запускаются с той же машины, и синтаксическая ошибка вылезает при первом же
|
||
запуске с номером строки. Разбор файлов DAG остался и по другому основанию —
|
||
их на машине не запускает никто.
|