fix(tradein/proxy_pool): истёкший прокси больше не выдаётся в аренду #3303

Merged
lekss361 merged 1 commit from fix/proxy-pool-honour-expires-at into main 2026-08-31 11:54:55 +00:00
Owner

Summary

Колонка scrape_proxies.expires_at существовала, но acquire() не читал её нигде (grep -n expires_at backend/app/services/proxy_pool.py до правки давал ноль вхождений).

Последствие, наблюдавшееся сегодня на живом проде: когда аренда порта у провайдера заканчивается, он начинает отдавать 407 Proxy Authentication Required или просто рвать соединение — а пул продолжает выдавать такой узел в аренду. Каждый запрос через него падает и классифицируется как ban_kind='infra', а 14 таких из 20 в скользящем окне прерывают прогон добора. То есть один просроченный порт роняет прогоны, пока человек вручную не заметит и не выключит узел.

Что сделано

  • Фильтр expires_at IS NULL OR expires_at > now() добавлен в обе ветки отбора: основную по affinity (proxy_pool.py:344) и fallback (proxy_pool.py:380).
  • expires_at IS NULL = «срок неизвестен», выдаче не мешает — отсекаются только реально просроченные.
  • Время считает БД, а не питон: иначе рассинхрон часовых поясов.
  • Пропуск по сроку логируется предупреждением — оператору нужен явный сигнал «порт просрочен», а не молчаливо сузившийся пул.
  • MAX_CONSECUTIVE_FAILS, health-трекинг, баны и защита выделенной affinity не тронуты — новое условие только добавлено через AND.

Test plan

  • pytest tests/services/test_proxy_pool.py — 62 passed (56 старых + 6 новых)
  • ruff check — чисто
  • CAST(:x AS type) во всех SQL, :x::type нет
  • Тесты: просроченный не выдаётся; NULL выдаётся как раньше; будущий срок выдаётся; fallback-ветка тоже уважает срок; предупреждение в логе
  • Проверено, что тест не «зелёный по построению»: FakeSession фильтрует по подстроке реального SQL, уберут фильтр — тест упадёт
  • Warning-SELECT идёт без FOR UPDATE, семантику SKIP LOCKED не ломает
  • code-reviewer: APPROVE, блокеров нет

Follow-up (не блокер)

Предупреждение печатается на каждый acquire(), пока просроченный узел остаётся enabled и незанятым — без дедупликации. acquire() вызывается пер-сессию, а не на каждый запрос, так что спам умеренный; если засорит логи на проде — дедуплицировать отдельной правкой.

## Summary Колонка `scrape_proxies.expires_at` существовала, но `acquire()` **не читал её нигде** (`grep -n expires_at backend/app/services/proxy_pool.py` до правки давал ноль вхождений). Последствие, наблюдавшееся сегодня на живом проде: когда аренда порта у провайдера заканчивается, он начинает отдавать `407 Proxy Authentication Required` или просто рвать соединение — а пул продолжает выдавать такой узел в аренду. Каждый запрос через него падает и классифицируется как `ban_kind='infra'`, а 14 таких из 20 в скользящем окне прерывают прогон добора. То есть **один просроченный порт роняет прогоны**, пока человек вручную не заметит и не выключит узел. ## Что сделано - Фильтр `expires_at IS NULL OR expires_at > now()` добавлен в **обе** ветки отбора: основную по affinity (`proxy_pool.py:344`) и fallback (`proxy_pool.py:380`). - `expires_at IS NULL` = «срок неизвестен», выдаче не мешает — отсекаются только реально просроченные. - Время считает **БД**, а не питон: иначе рассинхрон часовых поясов. - Пропуск по сроку логируется предупреждением — оператору нужен явный сигнал «порт просрочен», а не молчаливо сузившийся пул. - `MAX_CONSECUTIVE_FAILS`, health-трекинг, баны и защита выделенной affinity не тронуты — новое условие только добавлено через `AND`. ## Test plan - [x] `pytest tests/services/test_proxy_pool.py` — 62 passed (56 старых + 6 новых) - [x] `ruff check` — чисто - [x] `CAST(:x AS type)` во всех SQL, `:x::type` нет - [x] Тесты: просроченный не выдаётся; `NULL` выдаётся как раньше; будущий срок выдаётся; fallback-ветка тоже уважает срок; предупреждение в логе - [x] Проверено, что тест не «зелёный по построению»: `FakeSession` фильтрует по подстроке реального SQL, уберут фильтр — тест упадёт - [x] Warning-SELECT идёт без `FOR UPDATE`, семантику `SKIP LOCKED` не ломает - [x] code-reviewer: ✅ APPROVE, блокеров нет ## Follow-up (не блокер) Предупреждение печатается на каждый `acquire()`, пока просроченный узел остаётся `enabled` и незанятым — без дедупликации. `acquire()` вызывается пер-сессию, а не на каждый запрос, так что спам умеренный; если засорит логи на проде — дедуплицировать отдельной правкой.
lekss361 added 1 commit 2026-08-31 11:39:07 +00:00
fix(tradein/proxy_pool): истёкший прокси больше не выдаётся в аренду
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 14s
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
CI Trade-In / backend-tests (pull_request) Successful in 5m21s
a0a2f6ba5e
Колонка scrape_proxies.expires_at существовала, но acquire() её не читал
нигде. Когда аренда порта у провайдера заканчивается, он начинает отдавать
407 или просто рвать соединение, а пул продолжал выдавать такой узел. Каждый
запрос через него падал и классифицировался как ban_kind='infra', а 14 таких
из 20 в скользящем окне прерывают прогон добора. То есть один просроченный
порт ронял прогоны, пока человек вручную не заметит и не выключит узел.

Фильтр expires_at IS NULL OR expires_at > now() добавлен в обе ветки отбора —
основную по affinity и fallback. NULL означает «срок неизвестен» и выдаче не
мешает, отсекаются только реально просроченные. Время считает БД, не питон:
иначе рассинхрон часовых поясов.

Пропуск по сроку логируется предупреждением: оператору нужен явный сигнал
«порт просрочен», а не молчаливо сузившийся пул.
lekss361 merged commit b7c1c4d9d4 into main 2026-08-31 11:54:55 +00:00
lekss361 deleted branch fix/proxy-pool-honour-expires-at 2026-08-31 11:54:55 +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#3303
No description provided.