- Зачем:
- находка «это стоило бы проверять регулярно» решалась заново в каждом
тикете и каждый раз тянулась в make check-clickhouse. У неё есть
назначенный дом: даги качества данных, которые придут со следующими
уровнями хранилища.
- Что:
- добавлен раздел «Корректность процессов живёт в дагах DQ, а не в целях
make»: цели make отвечают «стенд собран», свойства данных — работа дага.
- назван фильтр: про полноту дня, свежесть слоя, сходимость витрины с
источником — это даг, а не цель.
- названа учебная сторона: даг идёт по расписанию, пишет историю проверок
и разбирается как обычная задача Airflow — так качество данных устроено
в бою.
- Проверка:
- make config-test
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
211 lines
18 KiB
Markdown
211 lines
18 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`, даже если по цене
|
||
они подошли бы смоуку.
|
||
|
||
## Карта целей
|
||
|
||
Стенд нужен трём целям из семи. Цена — замер 6 августа 2026 года, см. «Что
|
||
проверено».
|
||
|
||
| Цель | Что утверждает | Стенд | Цена |
|
||
|---|---|---|---|
|
||
| `make lint` | Код генератора отформатирован и проходит ruff | не нужен | 0,4 с |
|
||
| `make typecheck` | Типы генератора сходятся (ty) | не нужен | 0,5 с |
|
||
| `make test` | Генератор делает то, что обещает; схема события остаётся объявленным контрактом, а собранное из неё [описание выгрузки](../formats/clickstream-event.md) — свежим | не нужен | 40 с |
|
||
| `make config-test` | Compose разбирается, файлы DAG синтаксически целы, в diff нет пробельных ошибок. О работоспособности не говорит ничего | не нужен | 1 с |
|
||
| `make smoke` | Стенд **собран**: службы живы, порты отвечают, подключения настроены друг на друга. Вширь и по касательной к каждой службе. Единственная цель, которая здесь правда смоук | нужен | 8 с |
|
||
| `make check-clickhouse` | Всё, что спрашивают **у ClickHouse** и он отвечает сам: макросы, шарды, реплики, путь в keeper, ключ шардирования, очередь распределённых DDL | нужен | 7 с |
|
||
| `make check-services` | **Службы работают**: DAG запускается и доходит, топик создаётся и удаляется, Superset логинится и ходит в базу | нужен | 44 с |
|
||
|
||
## Правило: смоук обязан оставаться быстрым
|
||
|
||
`make smoke` — быстрая проверка для регулярного прогона: её гоняют не
|
||
задумываясь, и потому она обязана укладываться в секунды. Тяжёлые проверки в
|
||
неё включать не следует: тяжёлая — та, из-за которой смоук перестаёт быть
|
||
быстрым. Поодиночке это единицы и десятки секунд, вместе — минуты. Такие
|
||
проверки живут в `make check-services`.
|
||
|
||
На практике дорого обходится не работа, а ожидание службы: такт планировщика
|
||
Airflow, сессия Superset. `make check-clickhouse` создаёт таблицы, вставляет
|
||
строки и гоняет распределённые DDL — и укладывается в семь секунд, потому что
|
||
ClickHouse отвечает сразу.
|
||
|
||
Первый абзац — правило, второй — наблюдение, по которому тяжёлую проверку
|
||
узнают заранее, не замеряя.
|
||
|
||
## Какую проверку когда запускать
|
||
|
||
Деление целей ценно ровно до тех пор, пока оно не превратилось в «гонять
|
||
всегда всё».
|
||
|
||
| Когда | Что гонять |
|
||
|---|---|
|
||
| Правка в работе | `make config-test` и `make smoke` |
|
||
| PR или задача | плюс цели, которых правка касалась: DAG-и, Superset или Kafka — `check-services`; DDL, кластер или данные — `check-clickhouse` |
|
||
| Приёмка этапа | `make up` с нуля и все три цели на стенде |
|
||
|
||
Правки генератора добавляют к этому `make lint`, `make typecheck` и
|
||
`make test`: стенд им не нужен, а `make test` из них самая дорогая.
|
||
|
||
## Порогов по времени здесь нет
|
||
|
||
Цена в таблице — замеренное число с датой замера, а не назначенный порог.
|
||
Автоматической проверки времени в репозитории нет и заводить её не следует.
|
||
Число, вписанное в проверку, становится законом, которого никто не выбирал:
|
||
[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.
|
||
|
||
Что тогда стережёт цепочку постоянно — проверки на **настоящих** данных, тех,
|
||
что стенд произвёл сам: счётчики против манифеста зернового мира ловят
|
||
сломанный разбор (события уехали в брак — счёт разошёлся), ничего при этом не
|
||
вкладывая.
|
||
|
||
## Корректность процессов живёт в дагах 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` |
|
||
|
||
Общее у смоука и `check-services` — счёт проверок, обращение к Compose и две
|
||
проверки — вынесено в `scripts/stand-common.sh`; сам он не запускается.
|
||
Оттуда же приходят два решения, которые видно по счёту прогонов:
|
||
|
||
- **`check_containers_survived` стоит в конце обеих целей.** Убитый за память
|
||
контейнер Docker поднимает сам, и проверка здоровья об этом промолчит
|
||
(ADR 0004). Стенд нагружает `check-services`, а увидеть последствия нужно и
|
||
тому, кто гонял один смоук. Это единственная проверка, которая считается
|
||
дважды: 20 у смоука плюс 7 у `check-services` — это 26 разных проверок.
|
||
- **Зависимости машины считает только смоук.** «На машине есть Docker, curl и
|
||
jq» — вопрос к машине, а не к службам, и на оси он стоит рядом с «стенд
|
||
собран». Для `check-services` это условие запуска: без них он не начнёт
|
||
работу и громко скажет об этом, но ЗЕЛЁНО за это не печатает.
|
||
|
||
Вход в Airflow `check-services` выполняет заново — общих переменных у двух
|
||
скриптов нет. Отдельной проверкой этот вход тоже не считается: то же самое
|
||
утверждает смоук.
|
||
|
||
## Что проверено
|
||
|
||
Замеры 6 августа 2026 года, стенд поднят заранее; время `make up` в цену целей
|
||
не входит. Время взято по `time` и совпадает с тем, что цель печатает сама. Оно
|
||
плавает от прогона к прогону: смоук дал 8 секунд дважды (без проверки Kafka,
|
||
до её появления, — 5 и 6), `check-services` — 43 и 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` с его 44 секундами, хотя генератор пишет в 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 остался и по другому основанию —
|
||
их на машине не запускает никто.
|