fix(tradein/proxy): отличать ручное выключение узла от авто-выключения (#2610) #2652

Merged
bot-backend merged 1 commit from fix/tradein-proxy-disabled-reason into main 2026-08-05 10:18:03 +00:00
Collaborator

Summary

#2610 (находка ревью PR #2609). #2609 сделал mark_health(ok=True) безусловно ставящим enabled=true — это лечило реальный прод-инцидент (авто-выключенный узел больше никогда не проверялся, транзиентный сбой = вечный приговор). Побочка: узел, снятый оператором руками, молча возвращался в строй первой же ipify-пробой. Особенно больно в главном рабочем сценарии — узел выключили из-за бана площадкой, а ipify никого не банит → проба всегда проходит → узел воскресает гарантированно, и снимать приходится снова и снова.

Колонка scrape_proxies.disabled_reason text (миграция 209, ADD COLUMN IF NOT EXISTS, без DEFAULT — метаданные, без переписывания таблицы). NULL = не выключен вручную; авто-выключение по consecutive_fails тоже оставляет NULL, поэтому поведение #2609 полностью сохранено.

Почему текст, а не булев manually_disabled: #2600 п.1 добавит третью причину («бан площадкой») — она ляжет в ту же колонку как 'banned:avito' без новой миграции, и значение само документирует причину. Все проверки ветвятся на IS NULL, а не на значение.

Охват (ревью подтвердило исчерпывающим грепом — всего 4 сайта записи enabled):

  • mark_health ok-ветка — гейт disabled_reason IS NULL + лог пропуска воскрешения (раньше молча);
  • mark_health fail-ветка — причину не трогает (авто-выключенные остаются воскресаемыми);
  • bulk_upsert_proxies — второй unconditional-enable, которого issue не называл (его ON CONFLICT тоже безусловно включал узел при ре-апсерте);
  • patch_proxyenabled=False пишет причину, enabled=True безусловно сбрасывает её (иначе узел, выключенный однажды, больше никогда не был бы авто-восстановим).

run_proxy_healthcheck продолжает пробовать ручно-выключенные узлы (метрики/exit_ip свежие, узел готов к работе сразу после включения), но счётчик revived считает только настоящие воскрешения. disabled_reason отдаётся в GET /proxies и PATCH /proxies/{id} — оператор видит причину.

Test plan

  • Red/green: авто-выключенный воскресает (#2609 цел) · ручно-выключенный НЕ воскресает + пишется WARNING · ручное включение сбрасывает причину · bulk_upsert сохраняет ручное выключение при ре-апсерте
  • tests/services/test_proxy_pool.py + tests/test_admin_proxies.py = 45 passed; полный сьют 3277 passed (1 known pre-existing); ruff чисто
  • Post-deploy: миграция 209 применилась, GET /proxies отдаёт поле

Review

code-reviewer: APPROVE. Сам стэшил реализацию и убедился, что 3 новых теста реально красные на до-#2610 коде (не тавтология); проверил все переходы состояний (manual → auto-fail → auto-disable не «понижается» до авто-воскрешаемого); мок в тестах ветвится на реальных SQL-подстроках, а не хардкодит исход. Два косметических минора (пустая строка как reason; нет теста на ре-выключение без reason) — не блокеры.

Про будущее (вход в #2600 п.1): текущая колонка достаточна для третьей причины без миграции, но не даёт авто-истечения бана — если бан площадки временный, понадобится banned_until или парсинг timestamp из значения. Осознанно отложено до самой задачи.

Refs #2610

## Summary **#2610** (находка ревью PR #2609). #2609 сделал `mark_health(ok=True)` безусловно ставящим `enabled=true` — это лечило реальный прод-инцидент (авто-выключенный узел больше никогда не проверялся, транзиентный сбой = вечный приговор). Побочка: узел, снятый оператором **руками**, молча возвращался в строй первой же ipify-пробой. Особенно больно в главном рабочем сценарии — узел выключили из-за бана площадкой, а ipify никого не банит → проба всегда проходит → узел воскресает гарантированно, и снимать приходится снова и снова. **Колонка `scrape_proxies.disabled_reason text`** (миграция 209, `ADD COLUMN IF NOT EXISTS`, без DEFAULT — метаданные, без переписывания таблицы). `NULL` = не выключен вручную; авто-выключение по `consecutive_fails` тоже оставляет NULL, поэтому поведение #2609 полностью сохранено. **Почему текст, а не булев `manually_disabled`**: #2600 п.1 добавит третью причину («бан площадкой») — она ляжет в ту же колонку как `'banned:avito'` **без новой миграции**, и значение само документирует причину. Все проверки ветвятся на `IS NULL`, а не на значение. **Охват** (ревью подтвердило исчерпывающим грепом — всего 4 сайта записи `enabled`): - `mark_health` ok-ветка — гейт `disabled_reason IS NULL` + **лог** пропуска воскрешения (раньше молча); - `mark_health` fail-ветка — причину не трогает (авто-выключенные остаются воскресаемыми); - **`bulk_upsert_proxies`** — второй unconditional-enable, которого issue не называл (его `ON CONFLICT` тоже безусловно включал узел при ре-апсерте); - `patch_proxy` — `enabled=False` пишет причину, `enabled=True` **безусловно сбрасывает** её (иначе узел, выключенный однажды, больше никогда не был бы авто-восстановим). `run_proxy_healthcheck` продолжает пробовать ручно-выключенные узлы (метрики/exit_ip свежие, узел готов к работе сразу после включения), но счётчик `revived` считает только настоящие воскрешения. `disabled_reason` отдаётся в `GET /proxies` и `PATCH /proxies/{id}` — оператор видит причину. ## Test plan - [x] Red/green: авто-выключенный воскресает (#2609 цел) · ручно-выключенный НЕ воскресает + пишется WARNING · ручное включение сбрасывает причину · `bulk_upsert` сохраняет ручное выключение при ре-апсерте - [x] `tests/services/test_proxy_pool.py` + `tests/test_admin_proxies.py` = 45 passed; полный сьют 3277 passed (1 known pre-existing); ruff чисто - [ ] Post-deploy: миграция 209 применилась, `GET /proxies` отдаёт поле ## Review code-reviewer: **✅ APPROVE**. Сам стэшил реализацию и убедился, что 3 новых теста реально красные на до-#2610 коде (не тавтология); проверил все переходы состояний (manual → auto-fail → auto-disable не «понижается» до авто-воскрешаемого); мок в тестах ветвится на реальных SQL-подстроках, а не хардкодит исход. Два косметических минора (пустая строка как reason; нет теста на ре-выключение без reason) — не блокеры. Про будущее (вход в #2600 п.1): текущая колонка достаточна для третьей причины без миграции, но **не даёт авто-истечения** бана — если бан площадки временный, понадобится `banned_until` или парсинг timestamp из значения. Осознанно отложено до самой задачи. Refs #2610
bot-backend added 1 commit 2026-08-05 10:14:27 +00:00
fix(tradein/proxy): отличать ручное выключение узла от авто-выключения (#2610)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 7s
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 2m41s
bda7fa0950
PR #2609 сделал mark_health(ok=True) безусловно ставящим enabled=true —
это лечило реальный инцидент (авто-выключенный узел больше никогда не
проверялся и не мог вернуться). Побочка: узел, снятый оператором руками,
молча возвращался в строй первой же ipify-пробой. Особенно больно в
главном сценарии — узел выключили из-за бана площадкой, а ipify никого не
банит, значит проба всегда проходит и узел гарантированно воскресает.

Колонка scrape_proxies.disabled_reason (text, NULL = не выключен вручную):
выбран текст, а не булев флаг — #2600 п.1 добавит третью причину («бан
площадкой»), и она ляжет в ту же колонку без новой миграции. Авто-
выключение по consecutive_fails по-прежнему оставляет NULL, поэтому
поведение #2609 сохранено.

Флаг уважают все места записи enabled: mark_health, bulk_upsert_proxies
(второй unconditional-enable, issue его не называл) и patch_proxy —
ручное включение сбрасывает причину безусловно, иначе узел, выключенный
однажды, больше никогда не был бы авто-восстановим. Пропуск воскрешения
логируется (раньше происходило молча). Healthcheck продолжает пробовать
ручно-выключенные (метрики свежие), но счётчик revived считает только
настоящие воскрешения.

Refs #2610
bot-backend merged commit aa5bb76822 into main 2026-08-05 10:18:03 +00:00
bot-backend deleted branch fix/tradein-proxy-disabled-reason 2026-08-05 10:18:03 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2652
No description provided.