[tradein][backend][scrapers] Per-anchor watchdog timeout в run_*_city_sweep — sweep зависает навсегда #880

Closed
opened 2026-05-31 10:17:58 +00:00 by bot-backend · 1 comment
Collaborator

Проблема (live 2026-05-31)

run_cian_city_sweep / run_avito_city_sweep / run_yandex_city_sweep (scrape_pipeline.py) идут по якорям, каждый делает HTTP-скрейп. НЕТ per-anchor/per-request watchdog. Если запрос подвисает (прокси/коннект без эффективного таймаута сессии) — asyncio-задача блокируется НАВСЕГДА: heartbeat замерзает на середине якоря, anchors_done не растёт, run остаётся running (ловит только зомби-детект через 6ч), следующие якоря не идут.

Воспроизведено: cian-city-sweep #24 завис на anchor 3, #29 — на anchor 1 (heartbeat замёрз 5-9 мин, cancel вручную). Данные до зависания сохранены (+1124 cian listings), но sweep не дошёл до конца.

Фикс (дёшево, независимо от Celery-решения #581)

  1. Обернуть обработку каждого якоря в asyncio.wait_for(anchor_coro, timeout=ANCHOR_TIMEOUT) (180-300с). TimeoutError → logger.warning + errors_count+=1 + следующий якорь (не висеть).
  2. Проверить что curl_cffi/httpx-сессии скрейперов имеют явный timeout= на запрос (cian SERP-сессия — проверить; valuation/newbuilding имеют 25-30с).
  3. Применить ко всем трём sweep (общий хелпер).
  4. (опц.) N подряд timeout-якорей → mark_banned + abort.

Acceptance

Sweep с искусственно подвисшим якорем завершается за ≤ ANCHOR_TIMEOUT+overhead, помечает якорь failed, идёт дальше, mark_done в конце (не висит до 6ч). Тест: мок anchor-coro с asyncio.sleep(большой) → TimeoutError-ветка + counters.

Связано

#581 (scheduler discussion — watchdog нужен независимо от Celery), #559 (EPIC). Vault: inbox/2026-05-30-2050 раздел РОБУСТНОСТЬ.

## Проблема (live 2026-05-31) run_cian_city_sweep / run_avito_city_sweep / run_yandex_city_sweep (scrape_pipeline.py) идут по якорям, каждый делает HTTP-скрейп. НЕТ per-anchor/per-request watchdog. Если запрос подвисает (прокси/коннект без эффективного таймаута сессии) — asyncio-задача блокируется НАВСЕГДА: heartbeat замерзает на середине якоря, anchors_done не растёт, run остаётся running (ловит только зомби-детект через 6ч), следующие якоря не идут. Воспроизведено: cian-city-sweep #24 завис на anchor 3, #29 — на anchor 1 (heartbeat замёрз 5-9 мин, cancel вручную). Данные до зависания сохранены (+1124 cian listings), но sweep не дошёл до конца. ## Фикс (дёшево, независимо от Celery-решения #581) 1. Обернуть обработку каждого якоря в asyncio.wait_for(anchor_coro, timeout=ANCHOR_TIMEOUT) (180-300с). TimeoutError → logger.warning + errors_count+=1 + следующий якорь (не висеть). 2. Проверить что curl_cffi/httpx-сессии скрейперов имеют явный timeout= на запрос (cian SERP-сессия — проверить; valuation/newbuilding имеют 25-30с). 3. Применить ко всем трём sweep (общий хелпер). 4. (опц.) N подряд timeout-якорей → mark_banned + abort. ## Acceptance Sweep с искусственно подвисшим якорем завершается за ≤ ANCHOR_TIMEOUT+overhead, помечает якорь failed, идёт дальше, mark_done в конце (не висит до 6ч). Тест: мок anchor-coro с asyncio.sleep(большой) → TimeoutError-ветка + counters. ## Связано #581 (scheduler discussion — watchdog нужен независимо от Celery), #559 (EPIC). Vault: inbox/2026-05-30-2050 раздел РОБУСТНОСТЬ.
bot-backend added this to the Praktika demo — Mon 2026-06-01 milestone 2026-05-31 10:17:58 +00:00
bot-backend added the
scope/backend
scrapers
priority/p2
tradein
status/ready
labels 2026-05-31 10:17:58 +00:00
bot-backend self-assigned this 2026-05-31 10:21:18 +00:00
bot-backend added
status/wip
and removed
status/ready
labels 2026-05-31 10:21:20 +00:00
bot-backend added
status/review
and removed
status/wip
labels 2026-05-31 10:34:03 +00:00
bot-reviewer added
status/qa
and removed
status/review
labels 2026-05-31 10:41:15 +00:00
Collaborator

QA PASS — watchdog verified против merged diff (PR #881, merged 10:40:41). Core acceptance MET. (Лейбл status/review был stray-остатком рядом с status/qa — снимаю.)

Acceptance (по диффу, 2 файла, оба in-scope: scrape_pipeline.py + новый tests/test_anchor_watchdog.py):

  1. Per-anchor watchdog ANCHOR_TIMEOUT_SEC=240; каждый anchor обёрнут в await asyncio.wait_for(<anchor_coro>, timeout=ANCHOR_TIMEOUT_SEC). except TimeoutErrorlogger.warning(... timed out after %ds — skipping) + errors_count += 1 + continue (не abort/hang).
  2. Все 3 sweep — avito (run_avito_pipeline inline), cian (_cian_anchor_phases()), yandex (_yandex_anchor_phases()) — каждый под wait_for. Phase 3/4-код перенесён verbatim во вложенные async def-хелперы (re-indent + B023 default-args), логика та же.
  3. N подряд timeout → mark_banned (опц.) — cian+yandex инкрементят consecutive_failures, при >= MAXmark_banned + return. avito полагается на существующий AvitoBlockedError-abort (опционально, ОК).
  4. Тесты test_{avito,cian,yandex}_sweep_anchor_timeout_continues + test_cian_timeout_increments_counter_and_marks_done + test_anchor_timeout_sec_exported. Ассерты: errors_count>=1, lots_fetched>0 (следующий anchor выполнился), mark_done вызван, failed is None, anchors_done == len(anchors).

status/done, закрываю. Core-симптом (sweep висит навсегда → heartbeat замёрз → only 6ч zombie-detect) устранён: каждый anchor теперь bounded 240с.

Два минора (НЕ блокеры, рекомендую follow-up issue, не реоткрываю):

  • item-3 (cian SERP per-request timeout=) — в спеке был «проверить», в коде НЕ добавлен. Но wait_for(240s) уже ограничивает любой hang ⇒ core-проблема решена; per-request timeout лишь дал бы более быстрый fail (напр. 30с вместо полных 240с) — efficiency-nice-to-have, не корректность. Стоит завести лёгкий follow-up на явный timeout cian/avito SERP-сессий.
  • тест-механизм — таймаут симулируется патчем scrape_pipeline.asyncio.wait_for (raise TimeoutError на 1-м anchor), а не реальным asyncio.sleep(большой). Это разумно (избегает 240с-теста), except-ветка+counters+continue+mark_done покрыты; сам stdlib-механизм отмены wait_for гарантирован Python — не интеграционно-тестируется, но и не custom-логика.

Refs #581, #559.

✅ **QA PASS — watchdog verified против merged diff (PR #881, merged 10:40:41). Core acceptance MET.** (Лейбл `status/review` был stray-остатком рядом с `status/qa` — снимаю.) **Acceptance (по диффу, 2 файла, оба in-scope: `scrape_pipeline.py` + новый `tests/test_anchor_watchdog.py`):** 1. **Per-anchor watchdog ✅** — `ANCHOR_TIMEOUT_SEC=240`; каждый anchor обёрнут в `await asyncio.wait_for(<anchor_coro>, timeout=ANCHOR_TIMEOUT_SEC)`. `except TimeoutError` → `logger.warning(... timed out after %ds — skipping)` + `errors_count += 1` + **continue** (не abort/hang). 2. **Все 3 sweep ✅** — avito (`run_avito_pipeline` inline), cian (`_cian_anchor_phases()`), yandex (`_yandex_anchor_phases()`) — каждый под `wait_for`. Phase 3/4-код перенесён verbatim во вложенные `async def`-хелперы (re-indent + B023 default-args), логика та же. 3. **N подряд timeout → mark_banned (опц.) ✅** — cian+yandex инкрементят `consecutive_failures`, при `>= MAX` → `mark_banned` + `return`. avito полагается на существующий `AvitoBlockedError`-abort (опционально, ОК). 4. **Тесты ✅** — `test_{avito,cian,yandex}_sweep_anchor_timeout_continues` + `test_cian_timeout_increments_counter_and_marks_done` + `test_anchor_timeout_sec_exported`. Ассерты: `errors_count>=1`, `lots_fetched>0` (следующий anchor выполнился), `mark_done` вызван, `failed is None`, `anchors_done == len(anchors)`. → `status/done`, закрываю. Core-симптом (sweep висит навсегда → heartbeat замёрз → only 6ч zombie-detect) устранён: каждый anchor теперь bounded 240с. **Два минора (НЕ блокеры, рекомендую follow-up issue, не реоткрываю):** - **item-3 (cian SERP per-request `timeout=`)** — в спеке был «проверить», в коде НЕ добавлен. Но `wait_for(240s)` уже ограничивает любой hang ⇒ core-проблема решена; per-request timeout лишь дал бы более быстрый fail (напр. 30с вместо полных 240с) — efficiency-nice-to-have, не корректность. Стоит завести лёгкий follow-up на явный timeout cian/avito SERP-сессий. - **тест-механизм** — таймаут симулируется патчем `scrape_pipeline.asyncio.wait_for` (raise TimeoutError на 1-м anchor), а не реальным `asyncio.sleep(большой)`. Это разумно (избегает 240с-теста), `except`-ветка+counters+continue+mark_done покрыты; сам stdlib-механизм отмены `wait_for` гарантирован Python — не интеграционно-тестируется, но и не custom-логика. Refs #581, #559.
bot-qa added
status/done
and removed
status/qa
labels 2026-05-31 10:44:09 +00:00
bot-backend added the
status/qa
label 2026-05-31 10:51:00 +00:00
Sign in to join this conversation.
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#880
No description provided.