From c696fce40b09a4f094ecab376c5509395a0c67f3 Mon Sep 17 00:00:00 2001 From: Dmitry Dementiev Date: Fri, 7 Aug 2026 18:47:13 +0300 Subject: [PATCH] =?UTF-8?q?fix(stand):=20=D0=BD=D0=B0=D1=85=D0=BE=D0=B4?= =?UTF-8?q?=D0=BA=D0=B8=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20=E2=80=94=20?= =?UTF-8?q?=D0=B4=D0=B8=D0=B0=D0=B3=D0=BD=D0=BE=D0=B7=20=D0=BD=D0=B5=20?= =?UTF-8?q?=D1=83=D1=82=D0=B2=D0=B5=D1=80=D0=B6=D0=B4=D0=B0=D0=B5=D1=82=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=D1=87=D0=B8=D0=BD=D1=83,=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B8=20=D0=BD?= =?UTF-8?q?=D0=B5=20=D0=B2=D1=80=D1=83=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Зачем Холодное ревью нашло три места, где написанное сильнее сделанного. Что - Непустая таблица брака больше не выдаётся за доказательство сломанного разбора. Модельного дня у брака нет, обрамить его нечем, и строки прежних уроков лежат в нём месяц: после первого же урока с мусором проверка давала бы неверный диагноз навсегда. Теперь она даёт признак, по которому причину отличают, — сошлась недостача с числом брака или нет. - Комментарий у world-init обещал, что расхождение числа дней с STARTING_DAYS поймают счётчики. Это неправда в одну сторону: лишний день ложится за рамкой дат описи. Обещание убрано, дыра названа. - Довод «даг next_day этапа 5 продолжит ось» опирался на несуществующий этап; заменён настоящей причиной — заливка замыкает цепь разовых служб. - Потолок ожидания 300 с получил обоснование замером с кратностью, а сам скрипт — честную оговорку: его обещание работает на пустом стенде, на живом ждать нечего. - Третья, пропущенная ссылка на снятый порог скорости дня убрана из спеки. - Даты замеров в карте целей разведены: #42 менял три цели, а не шесть. Проверка Обе ветви диагноза сняты заново на живом стенде: без брака — «не доехали», с браком — признак различения. Стенд восстановлен, все 9 проверок зелёные, брака 0. make lint, typecheck, config-test, test (407 тестов) зелёные. Ссылка: #42 --- compose.yaml | 13 ++++++------- docs/architecture/testing.md | 17 ++++++++++------- docs/specs/2026-08-01-generator.md | 5 ++--- scripts/check-clickhouse.sh | 13 +++++++++---- scripts/wait-for-world.sh | 9 +++++++++ 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/compose.yaml b/compose.yaml index a702018..5b07ad9 100644 --- a/compose.yaml +++ b/compose.yaml @@ -251,8 +251,9 @@ services: # Что это за дни и каким мир обязан выйти — data/world-inventory.json. # # Число дней стоит здесь числом: YAML не читает Python, и одно из двух мест - # (второе — `STARTING_DAYS` в inventory.py) лишнее по построению. Разъедутся - # они — покраснеют счётчики `make check-clickhouse`. + # (второе — `STARTING_DAYS` в inventory.py) лишнее по построению. Правя одно, + # правьте второе — на страже тут никто не стоит: залей эта служба лишний + # день, он лёг бы за рамкой дат описи и остался бы незамеченным. # # Повторный `make up` заливает мир заново, и это не оплошность: `WatchID` у # событий те же, ReplacingMergeTree схлопнет повтор в ODS. Сырьё в STG при @@ -309,11 +310,9 @@ services: # упавшим для --wait. clickhouse-init: condition: service_completed_successfully - # По той же причине — и ещё по одной. Заливка стартового мира идёт - # последней в цепи разовых служб, и зависимого ей взять больше негде. - # Заодно это правда про порядок: даг `next_day` этапа 5 продолжает ось с - # того дня, на котором заливка остановилась, — Airflow приходит в мир, - # который уже есть. + # По той же причине. Заливка стартового мира идёт последней в цепи + # разовых служб, и зависимого ей взять больше негде: долгоживущие службы + # данных не ждут, а `airflow-init` эту цепь и так замыкает. world-init: condition: service_completed_successfully entrypoint: ["/bin/bash"] diff --git a/docs/architecture/testing.md b/docs/architecture/testing.md index 9640668..0c1569e 100644 --- a/docs/architecture/testing.md +++ b/docs/architecture/testing.md @@ -29,8 +29,9 @@ ## Карта целей -Стенд нужен трём целям из семи. Цена — замер 7 августа 2026 года, см. «Что -проверено». +Стенд нужен трём целям из семи. Цена — замер, см. «Что проверено»: `make test`, +`make smoke` и `make check-clickhouse` перемерены 7 августа 2026 года, их #42 +менял; остальные три стоят с замера 6 августа. | Цель | Что утверждает | Стенд | Цена | |---|---|---|---| @@ -204,11 +205,13 @@ Kafka → STG → ODS, и живёт он в `make check-clickhouse`. Что новая проверка **умеет краснеть**, снято двумя поломками того же дня, и проверялись обе ветви её диагноза. Снесли партицию `2026-06-03` в `ods.event_rep` — проверка покраснела, показала недостающий день и назвала -адрес: «события не доехали до ODS, начните с чтеца топика». Затем положили в -сырьё заведомо негодную строку — брак появился, и проверка сменила диагноз на -«сломан разбор, начните с `ods.event_errors_dist`». Строки опыта убраны, день -переигран `make generate-batch GENERATOR_DAY=2`; счёт вернулся к 401 185, и -это заодно показало дедупликацию: повтор дня не удвоил счёт под `FINAL`. +адрес: «таблица брака пуста, события не доехали до ODS, начните с чтеца +топика». Затем положили в сырьё заведомо негодную строку — брак появился, и +диагноз сменился на второй, с признаком для различения: «сойдётся недостача с +числом брака — сломан разбор, не сойдётся — брак от прежних опытов». Строки +опыта убраны, день переигран `make generate-batch GENERATOR_DAY=2`; счёт +вернулся к 401 185, и это заодно показало дедупликацию: повтор дня не удвоил +счёт под `FINAL`. Замеры 6 августа 2026 года, стенд поднят заранее. Время взято по `time` и совпадает с тем, что цель печатает сама. Оно плавает от прогона к прогону: diff --git a/docs/specs/2026-08-01-generator.md b/docs/specs/2026-08-01-generator.md index b5e23ef..b7981c3 100644 --- a/docs/specs/2026-08-01-generator.md +++ b/docs/specs/2026-08-01-generator.md @@ -888,9 +888,8 @@ пока число правдоподобно, само по себе оно ничего не сторожит, и подгонять поведение под его край не надо. Настоящий предел здесь другой и считается в другой валюте — дневной бюджет событий: события корзины - входят в те самые «около 50 тыс.», на которых стоят опись (#42) и - порог скорости дня. Упрётся будущая правка — двигать надо бюджет и его - причины, а не долю. + входят в те самые «около 50 тыс.», на которых стоит опись (#42). Упрётся + будущая правка — двигать надо бюджет и его причины, а не долю. Решено при исполнении #41 (2026-08-07). Первые два пункта — решения владельца грилингом 7 августа 2026 года, внесённые как есть; остальные приняты при diff --git a/scripts/check-clickhouse.sh b/scripts/check-clickhouse.sh index 85ae97c..51423f3 100755 --- a/scripts/check-clickhouse.sh +++ b/scripts/check-clickhouse.sh @@ -94,6 +94,11 @@ on_signal() { # Таблица брака здесь не второе утверждение, а объяснение первого. Утверждай мы # «брака нет», проверка краснела бы навсегда после первого же урока, где менти # нарочно отправил в топик мусор, — и краснела бы не о том. +# +# По той же причине непустой брак сам по себе ничего не доказывает: модельного +# дня у брака нет, обрамить его нечем, и строки прежних уроков лежат в нём +# месяц. Поэтому объяснение не утверждает причину, а даёт признак, по которому +# её отличают: сошлась недостача с числом брака — разбор, не сошлась — доставка. check_starting_world() { local expected actual broken first_date last_date @@ -122,11 +127,11 @@ check_starting_world() { FORMAT TSV")" printf 'Опись мира ожидает (дата, событий):\n%s\n' "$expected" >&2 printf 'В ods.event лежит:\n%s\n' "${actual:-— ничего —}" >&2 - if [[ -n "$broken" ]]; then - printf 'В ods.event_errors по классам брака:\n%s\n' "$broken" >&2 - fail 'счёт разошёлся с описью, и в таблице брака есть строки: сломан разбор — начните с ods.event_errors_dist и матвью ods.event_mv' + if [[ -z "$broken" ]]; then + fail 'счёт разошёлся с описью, а таблица брака пуста: события не доехали до ODS — начните с чтеца топика stg.hits_raw_kafka и матвью приёма stg.hits_raw_mv' fi - fail 'счёт разошёлся с описью, а таблица брака пуста: события не доехали до ODS — начните с чтеца топика stg.hits_raw_kafka и матвью приёма stg.hits_raw_mv' + printf 'В ods.event_errors по классам брака:\n%s\n' "$broken" >&2 + fail 'счёт разошёлся с описью, и в таблице брака есть строки. Сойдётся недостача с их числом — сломан разбор, смотрите матвью ods.event_mv; не сойдётся — брак остался от прежних опытов, а события не доехали: смотрите чтеца топика stg.hits_raw_kafka' } assert_ddl_queue_completed() { diff --git a/scripts/wait-for-world.sh b/scripts/wait-for-world.sh index df73bd3..9252c6a 100755 --- a/scripts/wait-for-world.sh +++ b/scripts/wait-for-world.sh @@ -15,10 +15,19 @@ # брака. Сломайся разбор — события уедут в `*_errors`, сумма сойдётся, ожидание # кончится, и поломку назовёт `make check-clickhouse`. Ждать здесь одних годных # событий значило бы висеть пять минут вместо внятного ответа. +# +# Обещание у скрипта ровно одно и только на пустом стенде: «мир доехал». На +# живом стенде мир уже лежит в ODS, условие истинно на первой же попытке, и +# ждать тут нечего — повторная заливка ничего не добавляет, её схлопнет +# дедупликация. Утверждать после неё «доехала именно эта заливка» скрипт не +# может и не пытается: у события нет отметки, которым прогоном оно приехало. set -Eeuo pipefail readonly ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" readonly INVENTORY="${ROOT_DIR}/data/world-inventory.json" +# Потолок ожидания — 300 с: больше чем вдесятеро от замеренных 22,5 с, за +# которые заливка отдаёт восемь дней в Kafka (карта проверок, «Что проверено»). +# Это не бюджет, а предел, за которым ждать бессмысленно и надо читать журнал. readonly ATTEMPTS=100 readonly PAUSE_SECONDS=3 # Как часто отчитываться о ходе: молчащая минуту команда выглядит зависшей.