(medium) reap_zombies может пометить живой sweep 'zombie' → терминальные mark_done/mark_failed молча no-op, риск дубль-sweep #1363

Closed
opened 2026-06-14 18:09:11 +00:00 by bot-backend · 1 comment
Collaborator

Описание

reap_zombies ставит status='zombie' любому run'у с heartbeat старше 6ч. Долгие фазы (IMV per-house, address-enrich, detail-loops) не emit'ят heartbeat внутри per-house цикла (см. house_imv_backfill.py:523-548, heartbeat только после завершения IMV или на исключении). Живой, но затянувшийся >6ч sweep будет помечен 'zombie'; после этого терминальные mark_done/mark_banned/mark_failed несут WHERE status='running' и становятся no-op (только warning) — финальный исход и счётчики теряются, run навсегда мислейблится 'zombie'. Также нет защиты от ре-триггера 'zombie'-source, пока его async-task ещё работает (has_running_run матчит только status='running') → риск конкурентного дубль-sweep против того же source.

Корень

Отсутствие частых heartbeat в долгих фазах + отсутствие cancel живой asyncio-задачи при reaping + has_running_run не учитывает свежие 'zombie'.

Evidence

scheduler.py:128-148:

UPDATE scrape_runs SET status='zombie', finished_at=NOW()
 WHERE status='running' AND (heartbeat_at IS NULL OR heartbeat_at < NOW() - CAST(:interval AS interval))

scrape_runs.py mark_done/mark_failed/mark_banned (149/174/207): ... WHERE id=:run_id AND status='running' → no-op если уже 'zombie'.

Suggested fix

Эмитить heartbeat чаще в долгих фазах (IMV, address-enrich, detail-loops) и/или отменять живой asyncio-task при reaping. При ре-триггере трактовать очень свежие 'zombie'-run'ы как потенциально живые, чтобы избежать конкурентного дубль-sweep.

Обоснование severity

medium: при reaping живого run'а теряются финальные счётчики и терминальный статус; дубль-sweep увеличивает внешнюю HTTP-нагрузку (ban-риск). Sweep-задачи идемпотентны (ON CONFLICT DO NOTHING + per-row SAVEPOINT), что ограничивает downstream-порчу. confidence 0.82.


week ревью 1 · multi-agent аудит 2026-06-14 · severity=medium · category=data-integrity

Затронутые файлы:

  • /Users/anton/Птица/gendesign/tradein-mvp/backend/app/services/scheduler.py
  • /Users/anton/Птица/gendesign/tradein-mvp/backend/app/services/scrape_runs.py
## Описание `reap_zombies` ставит `status='zombie'` любому run'у с heartbeat старше 6ч. Долгие фазы (IMV per-house, address-enrich, detail-loops) не emit'ят heartbeat внутри per-house цикла (см. `house_imv_backfill.py:523-548`, heartbeat только после завершения IMV или на исключении). Живой, но затянувшийся >6ч sweep будет помечен 'zombie'; после этого терминальные `mark_done`/`mark_banned`/`mark_failed` несут `WHERE status='running'` и становятся no-op (только warning) — финальный исход и счётчики теряются, run навсегда мислейблится 'zombie'. Также нет защиты от ре-триггера 'zombie'-source, пока его async-task ещё работает (`has_running_run` матчит только status='running') → риск конкурентного дубль-sweep против того же source. ## Корень Отсутствие частых heartbeat в долгих фазах + отсутствие cancel живой asyncio-задачи при reaping + has_running_run не учитывает свежие 'zombie'. ## Evidence `scheduler.py:128-148`: ```sql UPDATE scrape_runs SET status='zombie', finished_at=NOW() WHERE status='running' AND (heartbeat_at IS NULL OR heartbeat_at < NOW() - CAST(:interval AS interval)) ``` `scrape_runs.py` mark_done/mark_failed/mark_banned (149/174/207): `... WHERE id=:run_id AND status='running'` → no-op если уже 'zombie'. ## Suggested fix Эмитить heartbeat чаще в долгих фазах (IMV, address-enrich, detail-loops) и/или отменять живой asyncio-task при reaping. При ре-триггере трактовать очень свежие 'zombie'-run'ы как потенциально живые, чтобы избежать конкурентного дубль-sweep. ## Обоснование severity medium: при reaping живого run'а теряются финальные счётчики и терминальный статус; дубль-sweep увеличивает внешнюю HTTP-нагрузку (ban-риск). Sweep-задачи идемпотентны (ON CONFLICT DO NOTHING + per-row SAVEPOINT), что ограничивает downstream-порчу. confidence 0.82. --- _week ревью 1 · multi-agent аудит 2026-06-14 · severity=**medium** · category=`data-integrity`_ Затронутые файлы: - `/Users/anton/Птица/gendesign/tradein-mvp/backend/app/services/scheduler.py` - `/Users/anton/Птица/gendesign/tradein-mvp/backend/app/services/scrape_runs.py`
bot-backend added the
week ревью 1
label 2026-06-14 18:09:11 +00:00
bot-backend added the
scope/backend
label 2026-06-15 11:01:53 +00:00
Owner

🔍 week ревью 1 — верификация (ПОДТВЕРЖДЕНО)

Текущее состояние кода (git show forgejo/main): scheduler.py:reap_zombiesUPDATE scrape_runs SET status='zombie', finished_at=NOW() WHERE status='running' AND (heartbeat_at IS NULL OR heartbeat_at < NOW() - CAST(:interval AS interval)) (порог ZOMBIE_THRESHOLD_HOURS). scrape_runs.py:mark_done:149, mark_failed:176, mark_banned:208 — все WHERE id=:run_id AND status='running' → no-op (только warning) если run уже zombie. has_running_run матчит лишь status='running'.

Корень: Долгие фазы не emit'ят heartbeat внутри per-house цикла → живой >threshold sweep помечается zombie; затем терминальные mark_* молчат, финальный исход и счётчики теряются. Защиты от ре-триггера свежего zombie-source (пока его asyncio-task жив) нет → риск конкурентного дубль-sweep.

Что править:

  • Эмитить heartbeat чаще в долгих фазах (IMV per-house house_imv_backfill.py, address-enrich, detail-loops) — внутри цикла, не только по завершении.
  • И/или отменять живую asyncio-задачу при reaping.
  • При ре-триггере трактовать очень свежие zombie-run'ы как потенциально живые (расширить has_running_run или отдельный guard), чтобы избежать дубль-sweep.

Что посмотреть:

  • Код: house_imv_backfill.py (per-house heartbeat — issue ссылается на ~:523-548, проверить актуальные строки в текущей ветке); scrape_runs.py:update_heartbeat:117; has_running_run в scrape_runs.py. Тест: run с heartbeat старше порога, но с живым task → не должен ломать финализацию.
  • Vault: релевантных заметок по zombie-reaping не найдено. Смежно — inbox/2026-06-13-* (GG-форсайт sessions) и Audit_Cleanup session, но не про этот симптом.

Примечание: scheduler.py/scrape_runs.py в рабочей ветке feat/cian-city-sweep-schedule могут отличаться — верификация сделана против forgejo/main.

**🔍 week ревью 1 — верификация (ПОДТВЕРЖДЕНО)** **Текущее состояние кода (git show forgejo/main):** `scheduler.py:reap_zombies` — `UPDATE scrape_runs SET status='zombie', finished_at=NOW() WHERE status='running' AND (heartbeat_at IS NULL OR heartbeat_at < NOW() - CAST(:interval AS interval))` (порог `ZOMBIE_THRESHOLD_HOURS`). `scrape_runs.py:mark_done:149`, `mark_failed:176`, `mark_banned:208` — все `WHERE id=:run_id AND status='running'` → no-op (только warning) если run уже `zombie`. `has_running_run` матчит лишь `status='running'`. **Корень:** Долгие фазы не emit'ят heartbeat внутри per-house цикла → живой >threshold sweep помечается `zombie`; затем терминальные `mark_*` молчат, финальный исход и счётчики теряются. Защиты от ре-триггера свежего `zombie`-source (пока его asyncio-task жив) нет → риск конкурентного дубль-sweep. **Что править:** - Эмитить heartbeat чаще в долгих фазах (IMV per-house `house_imv_backfill.py`, address-enrich, detail-loops) — внутри цикла, не только по завершении. - И/или отменять живую asyncio-задачу при reaping. - При ре-триггере трактовать очень свежие `zombie`-run'ы как потенциально живые (расширить `has_running_run` или отдельный guard), чтобы избежать дубль-sweep. **Что посмотреть:** - Код: `house_imv_backfill.py` (per-house heartbeat — issue ссылается на ~`:523-548`, проверить актуальные строки в текущей ветке); `scrape_runs.py:update_heartbeat:117`; `has_running_run` в `scrape_runs.py`. Тест: run с heartbeat старше порога, но с живым task → не должен ломать финализацию. - Vault: релевантных заметок по zombie-reaping не найдено. Смежно — `inbox/2026-06-13-*` (GG-форсайт sessions) и Audit_Cleanup session, но не про этот симптом. Примечание: `scheduler.py`/`scrape_runs.py` в рабочей ветке `feat/cian-city-sweep-schedule` могут отличаться — верификация сделана против `forgejo/main`.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#1363
No description provided.