fix(tradein/proxy): узел, зарезервированный за Домкликом, до Домклика не доходит (#2800 A) #2802

Merged
bot-backend merged 1 commit from fix/2800a-proxy1-affinity into main 2026-08-09 18:15:56 +00:00
2 changed files with 67 additions and 8 deletions
Showing only changes of commit ce5df52259 - Show all commits

View file

@ -228,14 +228,21 @@ def acquire(db: Session, provider: str, *, run_id: int | None = None) -> ProxyLe
чужая только запасной вариант, чтобы источник не голодал при живых свободных узлах
чужой affinity (#2600).
Fallback НЕ трогает последний enabled-узел выделенной (не-'any') affinity см.
173_scrape_proxies_add_domclick_affinity.sql: у domclick ровно один узел (id=1),
намеренно вырезанный из общего пула, потому что QRATOR банит все прокси кроме этого
одного чистого residential-адреса. Если fallback заберёт его под avito/cian/yandex,
domclick останется без прокси вообще хуже, чем голодание исходного источника,
которое фикс призван устранить. Кандидат участвует в fallback, только если его
affinity='any' ИЛИ у этой affinity есть ДРУГОЙ enabled-узел (EXISTS-подзапрос)
т.е. выдача не обнулит доступность выделенной affinity целиком.
Fallback НЕ трогает последний enabled-узел выделенной (не-'any') affinity: если
fallback заберёт его под чужой источник, «свой» останется без прокси вообще хуже,
чем голодание исходного источника, которое фикс призван устранить. Кандидат
участвует в fallback, только если его affinity='any' ИЛИ у этой affinity есть ДРУГОЙ
enabled-узел (EXISTS-подзапрос) т.е. выдача не обнулит доступность выделенной
affinity целиком.
Исторический повод для этой защиты (173_scrape_proxies_add_domclick_affinity.sql
единственный residential-узел id=1, закреплённый за domclick, потому что QRATOR
банил остальные) снят миграцией 253 (#2800): живая проба показала, что как раз до
рабочего хоста Домклика (bff-search-web.domclick.ru) этот узел НЕ доходит, а
Авито/Яндекс через него работают резервация держала узел за источником, которому
он не годен, и прятала от тех, кому годен. Узлов с выделенной affinity на проде
сейчас нет, но САМА защита остаётся: значение 'domclick' допустимо констрейнтом, и
следующий выделенный узел должен получить её сразу, а не после повторного разбора.
ОБА запроса отсекают узлы с АКТИВНЫМ баном по ЭТОМУ provider'у
(scrape_proxy_source_bans.banned_until > now(), #2600 п.2) — узел, забаненный Авито,

View file

@ -0,0 +1,52 @@
-- 253_scrape_proxy_domclick_affinity_release.sql
-- Снять с узла резервацию provider_affinity='domclick' (#2800).
--
-- WHY (замер, не рассуждение — живая проба 09.08.2026, тракт сайдкар+camoufox,
-- POST /fetch на robots.txt рабочего хоста каждой площадки):
--
-- узел | affinity | avito | ekb.cian.ru | realty.yandex.ru | bff-search-web.domclick.ru
-- -----+----------+-------+--------------------+------------------+---------------------------
-- 1 | domclick | 200 | 200 «Ошибка — Циан»| 200 | 500 NS_ERROR_PROXY_BAD_GATEWAY
-- 9 | any | 200 | 200 | 200 | 200
-- 10 | any | 200 | 200 | 200 | 200
-- 11 | any | 200 | 200 | 200 | 200
--
-- Узел, закреплённый 173-й миграцией СПЕЦИАЛЬНО за Домкликом, до рабочего хоста
-- Домклика не доходит вообще (NS_ERROR_PROXY_BAD_GATEWAY на bff-search-web —
-- именно туда ходит боевой сбор, см. providers/domclick/serp.py::_BFF_BASE), при
-- этом Авито и Яндекс через него отвечают штатно. Резервация даёт ровно обратный
-- эффект задуманному: единственный источник, которому узел ГОДЕН НЕ БЫЛ, держал его
-- за собой, а два источника, которым он годен, его не видели —
-- acquire('avito'|'yandex') отбирает по provider_affinity IN (source,'any'), а
-- fallback этот узел не берёт (защита последнего узла выделенной affinity).
--
-- 'any', а НЕ enabled=false: узел жив для двух площадок из четырёх, выключать его
-- целиком — терять четверть и без того дефицитного пула (#2638).
--
-- WHAT:
-- provider_affinity='domclick' → 'any' для узлов, у которых affinity именно такая.
-- CHECK-констрейнт (173) не трогаем: значение 'domclick' остаётся допустимым, если
-- в пуле появится узел, который до Домклика реально доходит.
--
-- ЧТО ЭТА МИГРАЦИЯ НЕ ДЕЛАЕТ (граница честная):
-- Она НЕ чинит Домклик. acquire('domclick') и до неё видел все четыре узла
-- (affinity IN ('domclick','any')), т.е. шанс вытянуть узел 1 и потратить первый
-- бакет впустую был и остаётся 1/4 — закрывает это проба по паре «узел × источник»
-- (#2800 часть B), а не смена affinity. Здесь снимается только резервация.
--
-- IDEMPOTENCY / SAFETY:
-- Один UPDATE в транзакции; повторный прогон не находит строк (no-op) — auto-apply
-- strict на деплое это требует. Блокирующего DDL нет (см.
-- scripts/check-migration-lock-timeout.py: правило про ALTER/DROP/CREATE INDEX),
-- UPDATE берёт row-lock на единичные строки.
--
-- Dependencies: 157_scrape_proxies.sql, 173_scrape_proxies_add_domclick_affinity.sql
BEGIN;
UPDATE scrape_proxies
SET provider_affinity = 'any',
updated_at = now()
WHERE provider_affinity = 'domclick';
COMMIT;