Ревью PR #2609: domclick — ровно один узел (прод scrape_proxies.id=1),
намеренно вырезанный из общего пула через provider_affinity='domclick'
(см. 173_scrape_proxies_add_domclick_affinity.sql) — QRATOR банит всё,
кроме этого одного чистого residential-адреса. Fallback-запрос из
предыдущего коммита мог законно забрать его под avito/cian/yandex,
оставив domclick (сейчас исправно собирает: 6501 активных объявлений,
368/сутки) без прокси вообще — чинили бы один источник ценой полной
поломки другого.
- acquire(): fallback-SELECT дополнен условием "affinity='any' ИЛИ есть
ДРУГОЙ enabled-узел той же affinity" через коррелированный EXISTS-
подзапрос (WHERE + FOR UPDATE SKIP LOCKED + ORDER BY last_ok_at NULLS
LAST, id — сохранены). Кандидат с единственным enabled-узлом своей
выделенной affinity в fallback не участвует.
- Тесты: единственный domclick-узел → acquire('avito') возвращает None;
второй enabled domclick-узел появляется — fallback снова срабатывает.
- Починен мок FakeSession (tests/services/test_proxy_pool.py):
ветка "mark_health ok" раньше ставила enabled=True безусловно по
совпадению общей подстроки "SET consecutive_fails = 0" (одинаковой в
старом и новом SQL) — test_mark_health_ok_revives_disabled_proxy
проходил бы и против кода без реанимации. Теперь ставит enabled=True
только если в тексте SQL реально есть "enabled". Та же проблема была
и в fallback-ветке (protects_last_node переопределял логику в Python
независимо от SQL) — исправлено аналогично: применяется, только если
в SQL реально есть EXISTS-подзапрос.
Прод-замер: disabled-узлы никогда не перепроверялись (WHERE enabled в
run_proxy_healthcheck) — auto-disable по DISABLE_THRESHOLD необратим,
транзиентный сбой = вечный приговор (id 11 сгорел за ночь, будучи
физически исправным). acquire() при пустой выборке по provider_affinity
падал в None, морив источник голодом при живых свободных узлах чужой
affinity.
- run_proxy_healthcheck: disabled-узлы проверяются реже (DISABLED_RECHECK_MINUTES=60
либо last_check_at IS NULL); успешная проба реанимирует узел
(enabled=true через mark_health) и инкрементит новый счётчик revived.
- mark_health(ok=True) теперь безусловно ставит enabled=true (реанимация).
- acquire: вторым заходом при пустой выборке своей affinity берёт любой
свободный здоровый узел любой affinity (WARNING-лог), приоритет своих
сохранён.
- _probe_proxy классифицирует неуспех (timeout/connect_error/http_error/other)
в fail_kind — прокидывается в mark_health только для логирования; полноценное
разделение порогов транзиент/бан отложено (см. docstring mark_health).