fix(tradein/scraper): сводка «что сейчас не собирает» — лестница вех не видит стрик 0 (#2670) #2806

Merged
bot-backend merged 1 commit from fix/2670-stale-digest into main 2026-08-10 08:29:27 +00:00
Collaborator

Что не так

#2720 вылечил «алерт ровно один раз за серию» — теперь вехи 3, 6, 12, 24, 48… Но лестница шагает по подряд идущим завершённым failed/banned прогонам, и на проде 2026-08-10 это не отвечает на вопрос «что сломано сейчас».

Шесть источников не имели успешного прогона дольше 3× своего такта. Трое худших лестнице недоступны, и лишь один из трёх — из-за редких вех:

источник стрик сут. без успеха когда напомнит лестница
avito_full_load_exhaustive 0 49.5 никогда: 5 банов обнулил cancelled 09.08 (деплой убил бегущий прогон)
cian_history_backfill 0 42.1 никогда: прогонов нет с 30.06 (единственная строка за 42 дня — skipped, cian_cookies_expired)
avito_full_load 31 37.7 веха 48 при interval_days=7 → +17 прогонов ≈ 119 суток
avito_detail_backfill 5 5.2 веха 6 → завтра
domclick_city_sweep 5 5.1 веха 6 → завтра
domclick_detail_backfill 4 5.0 веха 6 → послезавтра

Поэтому уплотнение вех отвергнуто, а не отложено. Оно чинит только третью строку: у первых двух стрик равен нулю — уплотнять нечего. «Замолчал» там означает «перестал производить завершённые прогоны», а не «серия длиннее последней вехи». Ограничение вех по времени («не реже раза в неделю») не помогает по той же причине: без стрика нечего таймить.

Что сделано

Сводка из второго варианта задачи — та, которую вы называли более честной. Раз в сутки одно событие: источники, у которых max(finished_at) при status='done' старше 3× interval_days их расписания.

  • Календарная, а не по стрику → видит и молчащий источник, и обнулённый cancelled, и редкую веху.
  • Порог и сортировка — в тактах, не в сутках: 24 суток без сбора у 28-суточного rosreestr_quarter_poll норма, у суточного domclick_city_sweep авария.
  • Такт берётся из того же default_params.interval_days, которым расписание себя двигает (compute_next_run_at), дефолт 1.
  • Расписание без единого успеха считается от created_at и помечается явно — «никогда не собирал» не должно выглядеть как «судить нечем».
  • Вызов — в тике kit-scheduler_loop рядом с reap_zombies; best-effort, тик уронить не может.

Лестница не тронута. Она отвечает на другой вопрос («что сломалось только что»), стоит дёшево и после сводки её редкие вехи перестают быть проблемой: avito_full_load теперь называется каждый день.

Заодно: шапка модуля утверждала, что прод крутит legacy app.services.scheduler. Эта ветка удалена в #2397 Part C — строка пережила собственную правду и посылает правку сторожей не в тот файл.

Известный потолок

Момент последнего выпуска помнится в памяти процесса → рестарт scheduler'а (деплой) даёт лишний выпуск. Осознанный размен: альтернатива — таблица состояния (миграция) ради анти-спама у механизма, который заводится против молчания. Помечено # ponytail: с путём апгрейда.

Получателей в GlitchTip по-прежнему 0 (#2673) — сводка означает «событие записано», не «кто-то узнал».

Test plan

tradein-mvp/backend/tests/test_2670_stale_source_digest.py, снимок прода 2026-08-10 (10 расписаний: 6 просроченных + 4 контрольных).

  • Красный прогон на коде до правки: 14 failed, 3 passed — зелёные ровно три test_ladder_is_silent_for_the_worst_two (контроль: показывает, что лестница на трёх худших молчит, — он зелёный и до, и после).
  • Зелёный после: 17 passed.
  • Соседние: test_2670_streak_and_partial_coverage + test_scraper_kit_scheduler_parity + test_2703_zero_result_watchdog + test_scrape_skip_visibility — 140 passed.
  • ruff + ruff-format на изменённых файлах.

Критерий приёмки на проде (записан ДО факта)

2026-08-11, в первые сутки после деплоя в логе tradein-scraper обязана появиться ровно одна строка scheduler: N источников не собирают дольше 3× своего такта, и в ней обязаны быть названы cian_history_backfill и avito_full_load_exhaustive — те двое, у кого стрик 0. Нет строки → сводка не работает. Есть, но без этих двоих → порог посчитан не тем тактом.

Встречная проверка: rosreestr_quarter_poll (такт 28 сут, 24 сут без сбора) в списке быть не должен.

Refs #2670

## Что не так #2720 вылечил «алерт ровно один раз за серию» — теперь вехи 3, 6, 12, 24, 48… Но лестница шагает по **подряд идущим завершённым failed/banned прогонам**, и на проде 2026-08-10 это не отвечает на вопрос «что сломано сейчас». Шесть источников не имели успешного прогона дольше 3× своего такта. Трое худших лестнице недоступны, и **лишь один из трёх — из-за редких вех**: | источник | стрик | сут. без успеха | когда напомнит лестница | |---|---|---|---| | `avito_full_load_exhaustive` | 0 | 49.5 | никогда: 5 банов обнулил `cancelled` 09.08 (деплой убил бегущий прогон) | | `cian_history_backfill` | 0 | 42.1 | никогда: прогонов нет с 30.06 (единственная строка за 42 дня — `skipped`, cian_cookies_expired) | | `avito_full_load` | 31 | 37.7 | веха 48 при `interval_days=7` → +17 прогонов ≈ **119 суток** | | `avito_detail_backfill` | 5 | 5.2 | веха 6 → завтра | | `domclick_city_sweep` | 5 | 5.1 | веха 6 → завтра | | `domclick_detail_backfill` | 4 | 5.0 | веха 6 → послезавтра | **Поэтому уплотнение вех отвергнуто, а не отложено.** Оно чинит только третью строку: у первых двух стрик равен нулю — уплотнять нечего. «Замолчал» там означает «перестал производить завершённые прогоны», а не «серия длиннее последней вехи». Ограничение вех по времени («не реже раза в неделю») не помогает по той же причине: без стрика нечего таймить. ## Что сделано Сводка из второго варианта задачи — та, которую вы называли более честной. Раз в сутки одно событие: источники, у которых `max(finished_at) при status='done'` старше `3× interval_days` их расписания. - Календарная, а не по стрику → видит и молчащий источник, и обнулённый `cancelled`, и редкую веху. - Порог и сортировка — **в тактах, не в сутках**: 24 суток без сбора у 28-суточного `rosreestr_quarter_poll` норма, у суточного `domclick_city_sweep` авария. - Такт берётся из того же `default_params.interval_days`, которым расписание себя двигает (`compute_next_run_at`), дефолт 1. - Расписание без единого успеха считается от `created_at` и помечается явно — «никогда не собирал» не должно выглядеть как «судить нечем». - Вызов — в тике kit-`scheduler_loop` рядом с `reap_zombies`; best-effort, тик уронить не может. **Лестница не тронута.** Она отвечает на другой вопрос («что сломалось только что»), стоит дёшево и после сводки её редкие вехи перестают быть проблемой: `avito_full_load` теперь называется каждый день. Заодно: шапка модуля утверждала, что прод крутит legacy `app.services.scheduler`. Эта ветка удалена в #2397 Part C — строка пережила собственную правду и посылает правку сторожей не в тот файл. ## Известный потолок Момент последнего выпуска помнится в памяти процесса → рестарт scheduler'а (деплой) даёт лишний выпуск. Осознанный размен: альтернатива — таблица состояния (миграция) ради анти-спама у механизма, который заводится **против** молчания. Помечено `# ponytail:` с путём апгрейда. Получателей в GlitchTip по-прежнему 0 (#2673) — сводка означает «событие записано», не «кто-то узнал». ## Test plan `tradein-mvp/backend/tests/test_2670_stale_source_digest.py`, снимок прода 2026-08-10 (10 расписаний: 6 просроченных + 4 контрольных). - [x] Красный прогон на коде до правки: **14 failed, 3 passed** — зелёные ровно три `test_ladder_is_silent_for_the_worst_two` (контроль: показывает, что лестница на трёх худших молчит, — он зелёный и до, и после). - [x] Зелёный после: 17 passed. - [x] Соседние: `test_2670_streak_and_partial_coverage` + `test_scraper_kit_scheduler_parity` + `test_2703_zero_result_watchdog` + `test_scrape_skip_visibility` — 140 passed. - [x] ruff + ruff-format на изменённых файлах. ## Критерий приёмки на проде (записан ДО факта) **2026-08-11, в первые сутки после деплоя** в логе `tradein-scraper` обязана появиться ровно одна строка `scheduler: N источников не собирают дольше 3× своего такта`, и в ней обязаны быть названы `cian_history_backfill` и `avito_full_load_exhaustive` — те двое, у кого стрик 0. Нет строки → сводка не работает. Есть, но без этих двоих → порог посчитан не тем тактом. Встречная проверка: `rosreestr_quarter_poll` (такт 28 сут, 24 сут без сбора) в списке быть **не должен**. Refs #2670
bot-backend added 1 commit 2026-08-10 08:18:32 +00:00
fix(tradein/scraper): сводка «что сейчас не собирает» — лестница вех не видит стрик 0 (#2670)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 3m50s
a8a2163472
#2720 вылечил «алерт ровно один раз за серию» (вехи 3, 6, 12, 24, 48…), но лестница
шагает по подряд идущим завершённым failed/banned прогонам. Прод 2026-08-10: шесть
источников не собирали дольше 3× своего такта, и трое худших лестнице недоступны —
причём лишь один из трёх из-за редких вех:

  cian_history_backfill       42.1 сут, стрик 0  — прогонов нет с 30.06 вовсе
  avito_full_load_exhaustive  49.5 сут, стрик 0  — 5 банов обнулил 'cancelled' 09.08
  avito_full_load             37.7 сут, стрик 31 — веха 48 при interval_days=7 → 119 сут

Уплотнение вех чинит только третью строку: у первых двух стрик равен нулю, уплотнять
нечего. Поэтому добавлена сводка, считающая КАЛЕНДАРНЫЙ возраст последнего успешного
прогона по scrape_schedules: раз в сутки одно событие со списком источников, у которых
он старше 3× их такта. Просрочка меряется в тактах, а не в сутках — 24 сут без сбора у
28-суточного rosreestr_quarter_poll норма, а у суточного domclick_city_sweep авария.

Лестница не тронута: она отвечает на другой вопрос («что сломалось только что»).

Заодно: шапка модуля утверждала, что прод крутит legacy app.services.scheduler. Эта
ветка удалена в #2397 Part C — строка пережила собственную правду и посылала правку
сторожей не в тот файл.
bot-backend merged commit 12c189ac27 into main 2026-08-10 08:29:27 +00:00
bot-backend deleted branch fix/2670-stale-digest 2026-08-10 08:29:27 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2806
No description provided.