gendesign/tradein-mvp/backend/data/sql/228_scrape_proxies_browser_health.sql
bot-backend 9803708109
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 9s
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
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 3m5s
fix(tradein/proxy): проба узла ходит браузерным трактом, вердикт живёт отдельно от HTTP-пробы (#2723)
ipify-проба отвечает на «узел жив вообще», а сбор Авито с 02.08 (#2637) ходит через
сайдкар браузером: camoufox стартует С ЭТИМ прокси, потом навигация. Это разные
свойства узла, и до сих пор они писались в ОДИН счётчик: боевой /fetch репортил
mark_health(ok=False), а следующая (≤30 мин) успешная ipify-проба делала
consecutive_fails=0 + enabled=true. Дешёвая проба стирала вердикт дорогого тракта —
узел, мёртвый для браузера, выходил из карантина каждые полчаса и снова забирал прогон.

Разведено тем же приёмом, что #2711 (scrape_runs.ban_kind) — поле РЯДОМ, а не новое
значение существующего флага. Миграция 228: browser_fail_streak / browser_unfit_since /
browser_check_at. Успешная ipify их не трогает, провал браузерной пробы не трогает
enabled/consecutive_fails.

Проба: POST /fetch (одна навигация) на robots.txt Авито через сайдкар с прокси узла в
теле. /fetch, а не /fetch-json — последний сначала грузит главную площадки. source=
'generic', чтобы не забирать лок боевого инстанса и не релончить его.

Такт решён замером на проде: одна браузерная проба = 8.3с и один запуск camoufox,
боевая нагрузка сайдкара = ~1000 /fetch и ~190 запусков camoufox в сутки. Каждым
прогоном healthcheck по 4 узлам это +192 запуска (удвоение), поэтому такт 360 мин:
+16 запусков (+8%). Неподтверждённый провал такт не двигает — второе подтверждение
приходит на следующем прогоне (~30 мин), не через 6 часов.

Пометка НЕ выводит узел из пула (пул 4 узла, #2638): acquire отдаёт его последним
(ORDER BY) и громко логирует, если пригодных не осталось. Путь обратно — успешная
браузерная проба (browser_refit). Отказ, принадлежащий сайдкару или площадке, узлу не
засчитывается — иначе одна упавшая общая зависимость пометила бы весь пул (#2686).

Refs #2723
2026-08-06 17:43:42 +05:00

66 lines
5.5 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 228_scrape_proxies_browser_health.sql
-- Здоровье узла ОТДЕЛЬНО для браузерного тракта (#2723).
--
-- WHY:
-- `run_proxy_healthcheck` гоняет через узел обычный httpx-GET к ipify. Боевой сбор
-- Авито с 02.08 (#2637) ходит через сайдкар браузером: camoufox стартует С ЭТИМ
-- прокси (geoip-lookup на launch), потом навигация. Это разные свойства узла:
-- крошечный GET проходит там, где launch/навигация падает (`browser unavailable
-- (proxy may be down)` — все 90 записанных обрывов сбора именно такие).
--
-- Хуже того, оба свойства писались в ОДИН счётчик: боевой /fetch репортит
-- mark_health(ok=False) → consecutive_fails++, но следующая (≤30 мин) успешная
-- ipify-проба делает consecutive_fails=0 + enabled=true. Дешёвая проба СТИРАЛА
-- вердикт дорогого тракта, и узел, мёртвый для браузера, вечно возвращался в
-- выдачу. Это ровно ошибка #2686 (схлопывание двух диагнозов в один флаг) в
-- другом месте; разводим её тем же приёмом, что #2711 (`scrape_runs.ban_kind`) —
-- поле РЯДОМ, а не новое значение существующего флага.
--
-- WHAT (три колонки, ни одна не участвует в enabled/consecutive_fails):
-- browser_fail_streak — подряд-провалы ИМЕННО браузерной пробы, и только те, что
-- атрибутируются узлу (сайдкар лежит / страница пустая —
-- не считаются, см. proxy_pool._classify_browser_probe).
-- Успешная ipify-проба его НЕ обнуляет — в этом весь смысл.
-- browser_unfit_since — момент, когда streak дошёл до порога. NOT NULL = «жив для
-- HTTP, непригоден для браузера». acquire() такой узел НЕ
-- исключает (голодание хуже — #2600/#2638, пул 4 узла), а
-- отправляет в КОНЕЦ очереди выдачи: его возьмут, только
-- если свободных пригодных нет.
-- browser_check_at — когда браузерную пробу гоняли последний раз. Такт у неё
-- свой, редкий (BROWSER_PROBE_MINUTES): она стоит запуска
-- camoufox (~8с замерено на проде), ipify — миллисекунды.
--
-- IDEMPOTENCY / SAFETY:
-- ADD COLUMN IF NOT EXISTS × 3, аддитивно, без backfill'а: NULL/0 = «браузерную
-- пробу ещё не гоняли», ровно то состояние, в котором пул и находится. Ни одна
-- существующая выборка не меняет результат (все три колонки новые). Повторный
-- прогон — no-op (auto-apply strict на деплое это требует).
--
-- Dependencies: 157_scrape_proxies.sql
BEGIN;
ALTER TABLE scrape_proxies
ADD COLUMN IF NOT EXISTS browser_fail_streak integer NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS browser_unfit_since timestamptz,
ADD COLUMN IF NOT EXISTS browser_check_at timestamptz;
COMMENT ON COLUMN scrape_proxies.browser_fail_streak IS
'Подряд-провалы браузерной пробы (сайдкар + camoufox через ЭТОТ узел), '
'атрибутированные узлу. НЕ обнуляется успешной ipify-пробой — иначе дешёвая '
'проба стирает вердикт дорогого тракта (#2723). Обнуляется успешной браузерной '
'пробой. Порог → browser_unfit_since, см. proxy_pool.BROWSER_UNFIT_THRESHOLD.';
COMMENT ON COLUMN scrape_proxies.browser_unfit_since IS
'NOT NULL = узел жив для обычного HTTP, но браузерный тракт через него не '
'работает (#2723). Это НЕ enabled=false: узел остаётся в пуле и обслуживает '
'curl-путь, а acquire() лишь отдаёт его последним. Полное выключение по-прежнему '
'значит «узел мёртв целиком» (серия транспортных сбоев) либо решение оператора.';
COMMENT ON COLUMN scrape_proxies.browser_check_at IS
'Последняя браузерная проба. Такт свой, редкий (proxy_pool.BROWSER_PROBE_MINUTES): '
'одна такая проба = запуск camoufox (~8с на проде), против миллисекунд у ipify. '
'На неподтверждённом провале НЕ обновляется — чтобы следующий же цикл '
'healthcheck подтвердил/опроверг отказ, а не ждал полный такт.';
COMMIT;