fix(tradein/scraper): пустой пул прокси перестаёт стирать чекпоинт полного обхода #2834

Merged
bot-backend merged 1 commit from fix/2687-full-load-infra into main 2026-08-12 12:18:03 +00:00
Collaborator

Summary

Разбор шести ban_kind='infra' у avito_full_load (#2687) вывел на соседний дефект того же класса, что и #2686, живой на main.

NoProxyAvailableError — наследник RuntimeError, поэтому в run_avito_full_load / run_cian_full_load / run_yandex_full_load он попадал в общую ветку except RuntimeErrormark_failed. mark_failed, в отличие от mark_banned, не пишет done_buckets: чекпоинт всех пройденных бакетов терялся ровно на НАШЕМ отказе — том исходе, для которого #2686 явно требовал его сохранять наравне с блокировкой площадкой. Плюс ban_kind_of_exception отдавал для него 'unknown', хотя причина здесь не «не установлена» (#2764), а названа самим типом — см. proxy_errors.py: «это НАША инфраструктура (нет живого прокси), не внешний блок».

Достижимость на проде (замер 12.08, read-only)

  • под avito доступны три узла (id 9/10/11; id 1 забанен парой до 13.08);
  • BrowserFetcher меняет lease после _LEASE_ROTATE_AFTER_FAILS=3 подряд неудачных /fetch, а proxy_pool.acquire не выдаёт узел с consecutive_fails >= MAX_CONSECUTIVE_FAILS=3девять неудачных POST'ов опустошают пул под источник;
  • лестница ОДНОГО полного обхода при отказывающем сайдкаре делает до 24 POST'ов (4 бакета _AVITO_SWEEP_MAX_CONSECUTIVE_BLOCKED × 3 попытки × 2 внутренних httpx-ретрая fetch) — то есть проходит через это состояние заведомо.

Цена потери чекпоинта — прогон 3547 (09.08): 35 бакетов, 5496 объявлений за 2ч58м.

Что изменено

  • ban_kind_of_exception: NoProxyAvailableError'infra' (общий классификатор — единственное место, через которое ходят все вызывающие);
  • три run_*_full_load: ветка except NoProxyAvailableError ДО generic-RuntimeErrormark_banned + done_buckets + честный диагноз. Сиблинги правятся вместе: у всех трёх один и тот же чекпоинт и один и тот же провал.

Поведение при блокировке площадкой и при отказе сайдкара не меняется.

Test plan

  • tests/test_2687_pool_exhaustion_keeps_checkpoint.pyкрасный на коде до правки (замер против origin/main: assert 'unknown' == 'infra' + непойманный NoProxyAvailableError во всех трёх источниках), зелёный после;
  • регрессия: test_2686_ban_kind_split, test_2764_ban_kind_no_default, test_2674_avito_full_load_coverage, test_kit_browser_fetcher_proxy_pool, test_scraper_kit_pipeline_parity{,2}, test_2625_run_that_did_nothing — 145 passed, 1 skipped;
  • ruff (line-length 100) на изменённых файлах — чисто.

Без миграций, без правок инфраструктуры.

Refs #2687, #2686

## Summary Разбор шести `ban_kind='infra'` у `avito_full_load` (#2687) вывел на соседний дефект того же класса, что и #2686, живой на `main`. `NoProxyAvailableError` — наследник `RuntimeError`, поэтому в `run_avito_full_load` / `run_cian_full_load` / `run_yandex_full_load` он попадал в общую ветку `except RuntimeError` → `mark_failed`. `mark_failed`, в отличие от `mark_banned`, **не пишет `done_buckets`**: чекпоинт всех пройденных бакетов терялся ровно на НАШЕМ отказе — том исходе, для которого #2686 явно требовал его сохранять наравне с блокировкой площадкой. Плюс `ban_kind_of_exception` отдавал для него `'unknown'`, хотя причина здесь не «не установлена» (#2764), а названа самим типом — см. `proxy_errors.py`: «это НАША инфраструктура (нет живого прокси), не внешний блок». ### Достижимость на проде (замер 12.08, read-only) - под `avito` доступны **три** узла (id 9/10/11; id 1 забанен парой до 13.08); - `BrowserFetcher` меняет lease после `_LEASE_ROTATE_AFTER_FAILS=3` подряд неудачных `/fetch`, а `proxy_pool.acquire` не выдаёт узел с `consecutive_fails >= MAX_CONSECUTIVE_FAILS=3` → **девять** неудачных POST'ов опустошают пул под источник; - лестница ОДНОГО полного обхода при отказывающем сайдкаре делает до **24** POST'ов (4 бакета `_AVITO_SWEEP_MAX_CONSECUTIVE_BLOCKED` × 3 попытки × 2 внутренних httpx-ретрая `fetch`) — то есть проходит через это состояние заведомо. Цена потери чекпоинта — прогон **3547** (09.08): 35 бакетов, 5496 объявлений за 2ч58м. ## Что изменено - `ban_kind_of_exception`: `NoProxyAvailableError` → `'infra'` (общий классификатор — единственное место, через которое ходят все вызывающие); - три `run_*_full_load`: ветка `except NoProxyAvailableError` ДО generic-`RuntimeError` → `mark_banned` + `done_buckets` + честный диагноз. Сиблинги правятся вместе: у всех трёх один и тот же чекпоинт и один и тот же провал. Поведение при блокировке площадкой и при отказе сайдкара не меняется. ## Test plan - [x] `tests/test_2687_pool_exhaustion_keeps_checkpoint.py` — **красный на коде до правки** (замер против `origin/main`: `assert 'unknown' == 'infra'` + непойманный `NoProxyAvailableError` во всех трёх источниках), зелёный после; - [x] регрессия: `test_2686_ban_kind_split`, `test_2764_ban_kind_no_default`, `test_2674_avito_full_load_coverage`, `test_kit_browser_fetcher_proxy_pool`, `test_scraper_kit_pipeline_parity{,2}`, `test_2625_run_that_did_nothing` — 145 passed, 1 skipped; - [x] ruff (line-length 100) на изменённых файлах — чисто. Без миграций, без правок инфраструктуры. Refs #2687, #2686
bot-backend added 1 commit 2026-08-12 12:13:02 +00:00
fix(tradein/scraper): пустой пул прокси перестаёт стирать чекпоинт полного обхода
All checks were successful
CI Trade-In / backend-tests (pull_request) Successful in 3m56s
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
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
fb6a8f34b0
NoProxyAvailableError — наследник RuntimeError, поэтому в run_avito_full_load /
run_cian_full_load / run_yandex_full_load он попадал в общую ветку `except
RuntimeError` → mark_failed. mark_failed, в отличие от mark_banned, НЕ пишет
done_buckets: чекпоинт всех пройденных бакетов терялся ровно на НАШЕМ отказе —
том исходе, для которого #2686 явно требовал его сохранять наравне с блокировкой
площадкой. Плюс ban_kind_of_exception отдавал для него 'unknown', хотя причина
здесь не «не установлена», а названа самим типом (proxy_errors.py: «это НАША
инфраструктура (нет живого прокси), не внешний блок»).

Достижимость на проде (замер 12.08, read-only): под avito доступны три узла
(id 9/10/11; id 1 забанен парой до 13.08). BrowserFetcher меняет lease после
_LEASE_ROTATE_AFTER_FAILS=3 подряд неудачных /fetch, а proxy_pool.acquire не
выдаёт узел с consecutive_fails >= MAX_CONSECUTIVE_FAILS=3 — девять неудачных
POST'ов опустошают пул под источник. Лестница одного полного обхода при
отказывающем сайдкаре делает до 24 POST'ов (4 бакета × 3 попытки × 2 внутренних
httpx-ретрая), то есть проходит через это состояние заведомо. Цена — прогон 3547
(09.08): 35 бакетов, 5496 объявлений за 2ч58м.

Refs #2687, #2686
bot-backend merged commit 4d31a0ee82 into main 2026-08-12 12:18:03 +00:00
bot-backend deleted branch fix/2687-full-load-infra 2026-08-12 12:18:04 +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#2834
No description provided.