fix(tradein/proxy): узел, зарезервированный за Домкликом, до Домклика не доходит (#2800 A)
All checks were successful
CI / changes (pull_request) Successful in 9s
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 3m54s
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped

Живая проба 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
This commit is contained in:
bot-backend 2026-08-09 22:56:32 +05:00
parent f3bcb1a25f
commit ce5df52259
2 changed files with 67 additions and 8 deletions

View file

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