From ce5df522593d5d1a75e5000b4e0809aeac2584a4 Mon Sep 17 00:00:00 2001 From: bot-backend Date: Sun, 9 Aug 2026 22:56:32 +0500 Subject: [PATCH] =?UTF-8?q?fix(tradein/proxy):=20=D1=83=D0=B7=D0=B5=D0=BB,?= =?UTF-8?q?=20=D0=B7=D0=B0=D1=80=D0=B5=D0=B7=D0=B5=D1=80=D0=B2=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D1=8B=D0=B9=20=D0=B7=D0=B0=20?= =?UTF-8?q?=D0=94=D0=BE=D0=BC=D0=BA=D0=BB=D0=B8=D0=BA=D0=BE=D0=BC,=20?= =?UTF-8?q?=D0=B4=D0=BE=20=D0=94=D0=BE=D0=BC=D0=BA=D0=BB=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BD=D0=B5=20=D0=B4=D0=BE=D1=85=D0=BE=D0=B4=D0=B8=D1=82=20?= =?UTF-8?q?(#2800=20A)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Живая проба 09.08.2026 тем же трактом, что и работа (сайдкар tradein-browser → camoufox с этим прокси → навигация на 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 Узел 1 закреплён за Домкликом миграцией 173, но до рабочего хоста Домклика (bff-search-web.domclick.ru — туда ходит providers/domclick/serp.py) не доходит вообще, а Авито и Яндекс через него отвечают штатно. Резервация работала ровно наоборот задуманному: держала узел за единственным источником, которому он НЕ годен, и прятала от двух, которым годен (acquire отбирает по provider_affinity IN (source,'any'), а fallback этот узел не берёт — защита последнего узла выделенной affinity). Миграция 253: provider_affinity 'domclick' → 'any'. Не enabled=false — узел жив для двух площадок из четырёх, выключать его целиком значит терять четверть и без того дефицитного пула (#2638). CHECK-констрейнт не трогаем: значение остаётся допустимым для узла, который до Домклика реально дойдёт. Границы: это НЕ починка Домклика. acquire('domclick') и раньше видел все четыре узла, шанс вытянуть узел 1 и потратить первый бакет впустую был и остаётся 1/4 — закрывает это проба по паре «узел × источник» (часть B того же issue). Здесь снимается только резервация. Побочно: до части B Циан получает 1 шанс из 4 вытянуть узел, для Циана мёртвый; ближайший прогон Циана — 10.08 11:24Z, часть B идёт сегодня же. Refs #2800 --- .../backend/app/services/proxy_pool.py | 23 +++++--- ...scrape_proxy_domclick_affinity_release.sql | 52 +++++++++++++++++++ 2 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 tradein-mvp/backend/data/sql/253_scrape_proxy_domclick_affinity_release.sql diff --git a/tradein-mvp/backend/app/services/proxy_pool.py b/tradein-mvp/backend/app/services/proxy_pool.py index ed40dc63..5e2cb9c5 100644 --- a/tradein-mvp/backend/app/services/proxy_pool.py +++ b/tradein-mvp/backend/app/services/proxy_pool.py @@ -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) — узел, забаненный Авито, diff --git a/tradein-mvp/backend/data/sql/253_scrape_proxy_domclick_affinity_release.sql b/tradein-mvp/backend/data/sql/253_scrape_proxy_domclick_affinity_release.sql new file mode 100644 index 00000000..b1f63939 --- /dev/null +++ b/tradein-mvp/backend/data/sql/253_scrape_proxy_domclick_affinity_release.sql @@ -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;