feat(tradein/proxy): здоровье прокси по паре «узел × источник» (#2600 п.2) #2654
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#2654
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/2600-per-source-proxy-health"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Что было
#2600 п.1доводил распознанный бан площадкой до пула ГЛОБАЛЬНО:mark_bannedвыключал узел целиком —enabled=false,disabled_reason='banned:<source>'. Прод-реальность другая: Авито банит IP, а Яндекс/Циан через тот же IP ходят чисто. Итог:disabled_reasonблокирует авто-воскрешение (#2610) → нужен был ручной PATCH оператора.Что стало
Бан — свойство ПАРЫ «узел × источник» (новая таблица
scrape_proxy_source_bans, миграция 210):mark_bannedпишет строку(proxy_id, source, banned_until, ban_count)и БОЛЬШЕ НЕ трогаетenabled. Глобальное выключение остаётся только за оператором (#2610) и за авто-disable по серии ТРАНСПОРТНЫХ сбоев (mark_health);acquire(provider)в ОБОИХ заходах (своя affinity и fallback на чужую) отсекает узлы с активным баном по ЭТОМУ provider'у. Узел, забаненный Авито, остаётся первосортным для Яндекса — это и есть суть п.2;acquire(source)не останется ни одного кандидата — бан не пишется, WARNING зовёт пополнять пул (#2638). Голодание без прокси хуже, чем работа через забаненный узел;run_proxy_healthcheckв конце сносит бан-строки, истёкшие дольше 7 суток назад (bans_purgedв counters);GET /admin/proxiesиPATCH /admin/proxies/{id}отдаютsource_bans: [{source, banned_until, ban_count}]— без этого «узел включён, но не выдаётся» для оператора необъяснимо.Почему такие TTL / эскалация
SOURCE_BAN_BASE_HOURS = 6— площадки снимают IP-баны за часы, а не минуты (короче → вернём узел под тот же бан и сожжём прогон), но и не за сутки (узел дефицитный).base * 2^(ban_count-1): 6 → 12 → 24 → 48 → 72. Узел, который площадка банит раз за разом, отдыхает от неё всё дольше вместо того, чтобы жечь прогоны.SOURCE_BAN_MAX_HOURS = 72— дольше 3 суток бессмысленно: либо бан снят, либо узел мёртв и его чистит оператор.SOURCE_BAN_PURGE_DAYS = 7: purge НАМЕРЕННО отложенный, а неbanned_until < now(). Истёкшая строка уже ничего не блокирует, но хранитban_count— это единственный носитель памяти об эскалации. Снести раньше — и узел, которого банят каждые сутки, каждый раз начинал бы с 6 часов. Неделя без нового бана = пара чистая, эскалация с нуля (в коде есть комментарий «НЕ оптимизировать»).Конверсия прод-остатков п.1 (в миграции)
Узлы с
enabled=false AND disabled_reason LIKE 'banned:%'новый код не пишет и ничем не снимает — они висели бы выключенными вечно. Миграция конвертирует каждый в 6-часовой per-source бан (source= часть послеbanned:) и возвращает узел в строй (enabled=true, disabled_reason=NULL). Ручные выключения оператора (disabled_reasonбез префиксаbanned:) не трогаются.Что НЕ входит
mark_health— как и было,fail_kindидёт только в логи.tradein-mvp/frontendлистинг/admin/proxiesне рендерит (тамProxyHealthCardна/scraper/health), поэтому фронт не трогался.Как проверять на проде
Test plan
pytest tests/services/test_proxy_pool.py— 50 passed (новые: ban-строка вместо глобального disable, эскалация + потолок, acquire по своему/чужому source, истёкший бан, защита последнего узла с учётом активных банов, purge)pytest tests/test_admin_proxies.py— 17 passed (добавленsource_bansв листинге)pytest tests— 3316 passed, 1 failed (test_search_api.py::test_search_cache_hit— падает и на чистом main в том же окружении, к прокси отношения не имеет)Refs #2600