feat(tradein/scheduler): boot-reap — прогоны предыдущего контейнера снимаются на старте (#3122) #3123

Merged
bot-backend merged 1 commit from feat/3122-boot-reap into main 2026-08-27 09:23:44 +00:00
Collaborator

Реализация #3122 по свежим уликам сегодняшнего офлайна.

Что

reap_boot_zombies(db, boot_time) — однократно на старте scheduler_loop, до первого тика: прогоны running со started_at раньше старта процесса (минус минута на часовой дрейф) → zombie + маркер counters.boot_reaped=true.

Почему критерий точный, а не эвристика: прогон, стартовавший раньше собственного процесса планировщика, мёртв по построению — его сборщик жил в предыдущем контейнере и умер вместе с ним. Пульс в SQL не участвует вовсе (закреплено тестом) — ложные срабатывания класса #2702 (5-часовые прогоны с редким пульсом) невозможны.

Resume-бонус: _resume_decision пускает zombie к подхвату чекпоинта ТОЛЬКО с маркером boot_reaped — у boot-зомби «движущейся точки» (причина исключения 'zombie' из _RESUME_STATUSES, #2845) быть не может. Пороговый zombie без маркера по-прежнему отвергается (тест-инвариант). Вчерашние потерянные свипы при таком механизме подхватились бы со своих чекпоинтов.

Единственная ситуация, где критерий мог бы соврать

Два живых scraper-контейнера одновременно (новый репает прогоны дренящегося старого). Атомарный recreate деплоя (#1951: drain ДО пересоздания) это исключает — оговорено в докстринге.

Проверки

  • test_3122_boot_reap.py (3): SQL-критерий (started_at, минута запаса, без heartbeat, с маркером), boot-зомби подхватывается (красный на main по значению: status_zombie ≠ ok), пороговый — отвергается (инвариант)
  • resume/parity окрестности: 25 + 53 passed (test_930, test_3074×2, scheduler_parity, zero_result_watchdog)

Приёмка: после следующего рестарта контейнера — warning boot-reap в логах и ноль записей running старше старта процесса; при следующем ребуте хоста источники не слепнут на 6ч.

🤖 Generated with Claude Code

Реализация #3122 по свежим уликам сегодняшнего офлайна. ## Что `reap_boot_zombies(db, boot_time)` — однократно на старте scheduler_loop, до первого тика: прогоны `running` со `started_at` раньше старта процесса (минус минута на часовой дрейф) → `zombie` + маркер `counters.boot_reaped=true`. **Почему критерий точный, а не эвристика**: прогон, стартовавший раньше собственного процесса планировщика, мёртв по построению — его сборщик жил в предыдущем контейнере и умер вместе с ним. Пульс в SQL не участвует вовсе (закреплено тестом) — ложные срабатывания класса #2702 (5-часовые прогоны с редким пульсом) невозможны. **Resume-бонус**: `_resume_decision` пускает `zombie` к подхвату чекпоинта ТОЛЬКО с маркером `boot_reaped` — у boot-зомби «движущейся точки» (причина исключения 'zombie' из `_RESUME_STATUSES`, #2845) быть не может. Пороговый zombie без маркера по-прежнему отвергается (тест-инвариант). Вчерашние потерянные свипы при таком механизме подхватились бы со своих чекпоинтов. ## Единственная ситуация, где критерий мог бы соврать Два живых scraper-контейнера одновременно (новый репает прогоны дренящегося старого). Атомарный recreate деплоя (#1951: drain ДО пересоздания) это исключает — оговорено в докстринге. ## Проверки - `test_3122_boot_reap.py` (3): SQL-критерий (started_at, минута запаса, без heartbeat, с маркером), boot-зомби подхватывается (**красный на main по значению**: status_zombie ≠ ok), пороговый — отвергается (инвариант) - resume/parity окрестности: 25 + 53 passed (test_930, test_3074×2, scheduler_parity, zero_result_watchdog) Приёмка: после следующего рестарта контейнера — warning `boot-reap` в логах и ноль записей `running` старше старта процесса; при следующем ребуте хоста источники не слепнут на 6ч. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bot-backend added 1 commit 2026-08-27 09:17:50 +00:00
feat(tradein/scheduler): boot-reap — прогоны предыдущего контейнера снимаются на старте (#3122)
All checks were successful
CI Trade-In / 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 / changes (pull_request) Successful in 11s
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 4m40s
63956f0891
Прод-факт 27.08 (после ночного офлайна #3119): 4 прогона 'running' со
стартами до старта контейнера блокировали свои источники через
has_running_run до 6-часового порогового reap'а — до пяти часов слепоты
на источник ровно после простоя, когда догон нужнее всего.

Критерий — started_at < старт процесса планировщика (минус минута на
дрейф), пульс не участвует: ложные срабатывания класса #2702 (редкий
пульс длинных прогонов) невозможны по построению — живой прогон этого
процесса не может быть старше самого процесса.

Маркер counters.boot_reaped=true открывает boot-зомби подхват чекпоинта
(_resume_decision): у порогового zombie процесс может быть жив
(движущаяся точка — причина исключения 'zombie' из _RESUME_STATUSES),
у boot-зомби — гарантированно мёртв. Пороговый zombie без маркера
по-прежнему отвергается (закреплено тестом-инвариантом).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bot-backend merged commit dea08a0523 into main 2026-08-27 09:23:44 +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#3123
No description provided.