fix(tradein/avito): hard-timeout on detail-backfill fetch — prevent zombie-hang (#1950) #2028

Merged
lekss361 merged 1 commit from fix/tradein-avito-detail-backfill-hang into main 2026-06-27 20:29:28 +00:00
Owner

Summary

Прод-run detail-backfill (#423) завис 7.7ч → zombie (healthy ~2.4ч), backlog не двинулся (21 456 avito ждут detail, 29.3% enriched). Корень: fetch_detail в loop'е был без hard-timeout — один зависший fetch (camoufox/curl-stall) блокировал loop навсегда, budget-guard (проверяется в начале итерации) и heartbeat (каждые 25) молчали → reap_zombies красил весь run zombie.

Фикс (avito_detail_backfill.py):

  • fetch_detailasyncio.wait_for(timeout=avito_detail_fetch_timeout_s=90s); новый except TimeoutError (перед except Exception): листинг = failed + db.rollback() + continue (один зависший листинг не роняет run).
  • scraper._rotate_ip() → bounded wait_for(timeout=settle+30) + try/except (вторичный hang-вектор на changeip).
  • Новая настройка avito_detail_fetch_timeout_s (ENV-override) в config.
  • Admin-триггер POST /scrape/avito-detail-backfill (batch_size/budget_sec, 409-guard на параллельный run) — для on-demand прогона/верификации (schedule window-gated 09-12 UTC).

Test plan

  • ruff-100 clean, no :x::type
  • pytest tests/tasks/test_avito_detail_backfill.py → 11 passed (incl. test_backfill_fetch_timeout_skips_and_continues — воспроизводит регрессию run 423: первый fetch висит → wait_for отменяет → failed; второй → enriched; loop не зависает, mark_done)
  • code-reviewer APPROVE (except-порядок верный, wait_for-семантика ок для обоих fetch-режимов, admin-auth через middleware, существующая block/budget/snapshot логика не тронута)
  • post-deploy: docker exec tradein-backend curl -X POST localhost:8000/api/v1/admin/scrape/avito-detail-backfill?batch_size=200&budget_sec=600 -H "X-Authenticated-User: admin" → run завершается done с ненулевым enriched, heartbeat обновляется

Восстанавливает throughput detail-очереди → точные координаты (#1967) + house_type/kitchen для avito. Refs #1950

## Summary Прод-run detail-backfill (#423) завис **7.7ч → zombie** (healthy ~2.4ч), backlog не двинулся (21 456 avito ждут detail, 29.3% enriched). Корень: `fetch_detail` в loop'е был **без hard-timeout** — один зависший fetch (camoufox/curl-stall) блокировал loop навсегда, budget-guard (проверяется в начале итерации) и heartbeat (каждые 25) молчали → `reap_zombies` красил весь run zombie. Фикс (`avito_detail_backfill.py`): - `fetch_detail` → `asyncio.wait_for(timeout=avito_detail_fetch_timeout_s=90s)`; новый `except TimeoutError` (перед `except Exception`): листинг = failed + `db.rollback()` + continue (один зависший листинг не роняет run). - `scraper._rotate_ip()` → bounded `wait_for(timeout=settle+30)` + try/except (вторичный hang-вектор на changeip). - Новая настройка `avito_detail_fetch_timeout_s` (ENV-override) в config. - **Admin-триггер `POST /scrape/avito-detail-backfill`** (batch_size/budget_sec, 409-guard на параллельный run) — для on-demand прогона/верификации (schedule window-gated 09-12 UTC). ## Test plan - [x] ruff-100 clean, no `:x::type` - [x] pytest `tests/tasks/test_avito_detail_backfill.py` → 11 passed (incl. `test_backfill_fetch_timeout_skips_and_continues` — воспроизводит регрессию run 423: первый fetch висит → wait_for отменяет → failed; второй → enriched; loop не зависает, mark_done) - [x] code-reviewer ✅ APPROVE (except-порядок верный, wait_for-семантика ок для обоих fetch-режимов, admin-auth через middleware, существующая block/budget/snapshot логика не тронута) - [ ] post-deploy: `docker exec tradein-backend curl -X POST localhost:8000/api/v1/admin/scrape/avito-detail-backfill?batch_size=200&budget_sec=600 -H "X-Authenticated-User: admin"` → run завершается `done` с ненулевым `enriched`, heartbeat обновляется Восстанавливает throughput detail-очереди → точные координаты (#1967) + house_type/kitchen для avito. Refs #1950
lekss361 added 1 commit 2026-06-27 20:28:47 +00:00
fix(tradein/avito): hard-timeout on detail-backfill fetch — prevent zombie-hang (#1950)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
e3adcd47be
Prod run 423 завис на 7.7ч (healthy ~2.4ч) → reaped как zombie → detail-backlog
не уменьшился (21456 листингов ждут detail, 29.3% enriched).

Корень: fetch_detail(...) в loop'е без hard-timeout. Один зависший fetch
(camoufox/browser hang или curl-stall) блокировал loop навсегда → budget-guard
(раз в итерацию) не срабатывал → heartbeat (раз в 25 attempts) не обновлялся
→ run zombie. Вторичный hang-вектор: _rotate_ip() на блок — тоже без timeout.

Fix:
- asyncio.wait_for(fetch_detail, timeout=avito_detail_fetch_timeout_s=90s):
  зависший fetch отменяется → TimeoutError → листинг failed, loop идёт дальше.
  TimeoutError ловится ДО общего Exception (TimeoutError ⊂ OSError ⊂ Exception),
  порядок: AvitoBlocked/RateLimited → TimeoutError → Exception.
- bound rotate: asyncio.wait_for(_rotate_ip, timeout=settle+30s) + try/except —
  зависший changeip не роняет run.
- config: avito_detail_fetch_timeout_s (ENV AVITO_DETAIL_FETCH_TIMEOUT_S).
- admin: POST /scrape/avito-detail-backfill (batch_size, budget_sec) — on-demand
  прогон/верификация (основной schedule window-gated 09-12 UTC).

Восстанавливает throughput detail-очереди (точные coords #1967 + house_type/kitchen).
lekss361 merged commit 2ebf7468cd into main 2026-06-27 20:29:28 +00:00
lekss361 deleted branch fix/tradein-avito-detail-backfill-hang 2026-06-27 20:29:28 +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#2028
No description provided.