fix(tradein/avito): брейкер добора считает долю только по отказам площадки — сайдкар/пустой пул не рвут прогон и не красят его banned #3367

Merged
bot-backend merged 2 commits from fix/3288-breaker-block-kind into main 2026-09-05 19:38:48 +00:00
Collaborator

Refs #3288, часть B (часть A — PR #3357, часть C — сайдкар, отдельно). Прогон 5425: 41 из 48 «блоков» были отказами нашего тракта, а прогон оборван по ratio и помечен banned при 41 живом успехе.

Что сделано

  • backfill_block_breaker.py: record_block(kind=BAN_KIND_PLATFORM) — в окно доли только platform; не-platform уходит в record_failure() (знаменатель окна; серия и safety-net не двигаются — иначе на коротком снапшоте тот же обрыв прилетел бы через safety_net).
  • avito_detail_backfill.py: _iter_causes/_caused_by_empty_pool — по типу в цепочке __cause__, не по тексту (#3272); пустой пул → mark_failed("пул прокси пуст — к площадке не ходили") + counters.no_proxy_stop=1, исход failed (тождество #3338 цело); ban_kind считается один раз — в брейкер и в перепись.
  • scrape_runs.py mark_backfill_finished: диагноз впервые влияет на статус — при dominant == infra mark_banned не зовётся: mark_failed, если produced == 0 (наша поломка), иначе mark_done; причина называет диагноз явно. При доминировании platform — как было. Узел общий → покрывает и финализацию yandex/domclick/cian без правки их задач.

Приёмка из комментария #3288 закреплена тестами: 20 подряд отказов сайдкара при снапшоте больше окна НЕ дают abort_reason=='ratio'; 14 настоящих AvitoBlockedError из 20 — дают (#3184 не изменён).

Тесты

test_3288b_block_kind_ratio.py (9): 106 passed, 1 skipped (-k breaker/avito_detail/3338/3184/3288). Фальсификация (откат app/): abort_reason='ratio' при 20 отказах сайдкара — assert 'ratio' is None, прогон встал на 20 из 30 — по значению (юнит брейкера при откате даёт TypeError — «возможности нет», величину несёт бэкфилл-тест).

Прод-приёмка

Следующие avito_detail_backfill: прогоны с доминирующим infra не banned и не обрываются по ratio; no_proxy_stop=1 при пустом пуле.

Refs #3288, часть B (часть A — PR #3357, часть C — сайдкар, отдельно). Прогон 5425: 41 из 48 «блоков» были отказами нашего тракта, а прогон оборван по `ratio` и помечен `banned` при 41 живом успехе. ## Что сделано - `backfill_block_breaker.py`: `record_block(kind=BAN_KIND_PLATFORM)` — в окно доли только `platform`; не-platform уходит в `record_failure()` (знаменатель окна; серия и safety-net не двигаются — иначе на коротком снапшоте тот же обрыв прилетел бы через safety_net). - `avito_detail_backfill.py`: `_iter_causes`/`_caused_by_empty_pool` — по типу в цепочке `__cause__`, не по тексту (#3272); пустой пул → `mark_failed("пул прокси пуст — к площадке не ходили")` + `counters.no_proxy_stop=1`, исход `failed` (тождество #3338 цело); `ban_kind` считается один раз — в брейкер и в перепись. - `scrape_runs.py` `mark_backfill_finished`: диагноз впервые влияет на статус — при `dominant == infra` `mark_banned` не зовётся: `mark_failed`, если `produced == 0` (наша поломка), иначе `mark_done`; причина называет диагноз явно. При доминировании platform — как было. Узел общий → покрывает и финализацию yandex/domclick/cian без правки их задач. Приёмка из комментария #3288 закреплена тестами: 20 подряд отказов сайдкара при снапшоте больше окна НЕ дают `abort_reason=='ratio'`; 14 настоящих `AvitoBlockedError` из 20 — дают (#3184 не изменён). ## Тесты `test_3288b_block_kind_ratio.py` (9): `106 passed, 1 skipped` (-k breaker/avito_detail/3338/3184/3288). Фальсификация (откат `app/`): `abort_reason='ratio' при 20 отказах сайдкара — assert 'ratio' is None`, прогон встал на 20 из 30 — по значению (юнит брейкера при откате даёт TypeError — «возможности нет», величину несёт бэкфилл-тест). ## Прод-приёмка Следующие avito_detail_backfill: прогоны с доминирующим `infra` не `banned` и не обрываются по `ratio`; `no_proxy_stop=1` при пустом пуле.
bot-backend added 1 commit 2026-09-05 19:10:49 +00:00
fix(tradein): вид блока решает обрыв по доле и статус прогона (#3288 часть B)
Some checks failed
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Failing after 5m19s
1a693192e1
Прогон 5425 оборвался по доле блоков и получил статус banned на 48 «блоках»,
из которых 41 был отказом нашего сайдкара: record_block() вида не принимал,
поэтому infra падал в числитель скользящего окна #3184 наравне с настоящим
баном, а mark_backfill_finished считал диагноз только ради телеметрии.

- record_block(kind): в числитель идёт только platform; всё остальное — в
  знаменатель (как record_failure), мимо серии и safety-net;
- NoProxyAvailableError у avito — не блок и не отказ площадки: прогон
  завершается no_proxy_stop=1 + mark_failed «пул прокси пуст» (как домклик
  после #3283); опознаётся по цепочке __cause__, не по подстроке (#3272);
- статус banned — только при доминировании platform; при infra прогон
  получает failed (нулевой результат) или done, с честной причиной.
Author
Collaborator

Deep-ревью BLOCK + полный CI (2 failed, 5462 passed): test_3196_yandex_ban_kind.py:147 (yandex 5xx = infra → ожидает banned, получил failed) и test_2764_ban_kind_no_default.py:81 (ban_kinds={infra} → ровно один mark_banned, получил []). Это не устаревшие ожидания: #3196 вводился ровно ради того, чтобы infra доезжал до колонки scrape_runs.ban_kind, а её пишет только mark_banned (scrape_runs.py:800); читатели — admin.py:2597 и SELECT ban_kind, count(*) из #2764. Понижение статуса при доминировании infra стирает диагноз из строки прогона. Плюс cian финализируется мимо этого узла (app/services/scheduler.py:174mark_banned(ban_kind=…)) — расхождение по источникам.

Решение по PR: п.3 из #3288 («не помечать banned при доминировании infra») из этого PR убирается — статусная семантика остаётся прежней (banned + ban_kind='infra'), оба контракта #3196/#2764 в силе, тесты не правятся. Остаются пп.1-2: record_block(kind) — в окно доли только platform; NoProxyAvailableErrorno_proxy_stop/mark_failed (это не бан вовсе). Смена статуса для infra-прогонов — отдельное решение с переносом ban_kind в mark_failed/mark_done и выравниванием cian (#3288, новый пункт).

Medium из ревью (у infra-прогона нет стоп-крана кроме budget_sec — 1600 попыток при мёртвом сайдкаре) — в тот же новый пункт.

Deep-ревью ❌ BLOCK + полный CI (`2 failed, 5462 passed`): `test_3196_yandex_ban_kind.py:147` (yandex 5xx = infra → ожидает `banned`, получил `failed`) и `test_2764_ban_kind_no_default.py:81` (`ban_kinds={infra}` → ровно один `mark_banned`, получил `[]`). Это не устаревшие ожидания: #3196 вводился ровно ради того, чтобы infra доезжал до колонки `scrape_runs.ban_kind`, а её пишет **только `mark_banned`** (`scrape_runs.py:800`); читатели — `admin.py:2597` и `SELECT ban_kind, count(*)` из #2764. Понижение статуса при доминировании infra стирает диагноз из строки прогона. Плюс cian финализируется мимо этого узла (`app/services/scheduler.py:174` → `mark_banned(ban_kind=…)`) — расхождение по источникам. **Решение по PR:** п.3 из #3288 («не помечать `banned` при доминировании infra») из этого PR **убирается** — статусная семантика остаётся прежней (`banned` + `ban_kind='infra'`), оба контракта #3196/#2764 в силе, тесты не правятся. Остаются пп.1-2: `record_block(kind)` — в окно доли только `platform`; `NoProxyAvailableError` → `no_proxy_stop`/`mark_failed` (это не бан вовсе). Смена статуса для infra-прогонов — отдельное решение с переносом `ban_kind` в `mark_failed`/`mark_done` и выравниванием cian (#3288, новый пункт). Medium из ревью (у infra-прогона нет стоп-крана кроме `budget_sec` — 1600 попыток при мёртвом сайдкаре) — в тот же новый пункт.
Light1YT added 1 commit 2026-09-05 19:28:38 +00:00
fix(tradein): понижать статус только у прогона, который работу сделал (#3288)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
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 4m59s
c82ec7c3af
Прошлая правка понижала banned→failed/done по одному диагнозу infra и ломала
обратный контракт: нулевой прогон с infra (yandex 5xx #3196, финализатор #2764)
получал 'failed' — настоящий бан площадки, опознанный как infra, прятался под
«нашу поломку». Хуже исходного дефекта: 2 красных теста в полном прогоне.

Понижение сужено до случая прогона 5425 — dominant='infra' И produced > 0:
брейкер оборвал по доле, а карточки при этом обогащались → 'done'. Нулевой
прогон остаётся 'banned' (честность несёт ban_kind), пустая перепись — тем
более: dominant='unknown', статус не трогаем.

Тесты: контроль на обратную ошибку (ноль результата → banned+infra) и на
пустой census (→ banned+unknown); основной кейс {'infra': 20} при 10
обогащённых — не banned.
Author
Collaborator

Поправка к моему комментарию выше — доводка нашла более точный разделитель, и п.3 остаётся в суженном виде:

  • падали не «источники без ban_kinds» (там dominant='unknown'banned как раньше), а чистый infra с нулевым результатом — ровно то, что #2764 (ban_kinds={infra}) и #3196 (yandex 5xx) закрепляют как banned + ban_kind='infra';
  • кейс #3288 (прогон 5425) от них отличает produced > 0: брейкер оборвал прогон, обогативший 41 карточку — это не бан площадки и не наша полная поломка. Условие сужено до dominant == infra AND produced > 0 → mark_done (не failed); нулевой infra-прогон — прежнее banned с диагнозом в колонке ban_kind.

Контракты #3196/#2764 не тронуты, полный suite 5429 passed; добавлены контроли обратной ошибки по значению (_stays_banned, _without_census_stays_banned). Диагноз infra у done-прогонов с результатом живёт в counters.ban_kinds (колонку ban_kind пишет только mark_banned) — это осознанно: такой прогон в SELECT ban_kind считаться баном и не должен. Стоп-кран для infra-серий и выравнивание cian — #3288 п.5.

Поправка к моему комментарию выше — доводка нашла более точный разделитель, и п.3 остаётся в **суженном** виде: - падали не «источники без `ban_kinds`» (там `dominant='unknown'` → `banned` как раньше), а **чистый infra с нулевым результатом** — ровно то, что #2764 (`ban_kinds={infra}`) и #3196 (yandex 5xx) закрепляют как `banned` + `ban_kind='infra'`; - кейс #3288 (прогон 5425) от них отличает `produced > 0`: брейкер оборвал прогон, обогативший 41 карточку — это не бан площадки и не наша полная поломка. Условие сужено до `dominant == infra AND produced > 0 → mark_done` (не `failed`); нулевой infra-прогон — прежнее `banned` с диагнозом в колонке `ban_kind`. Контракты #3196/#2764 не тронуты, полный suite `5429 passed`; добавлены контроли обратной ошибки по значению (`_stays_banned`, `_without_census_stays_banned`). Диагноз infra у `done`-прогонов с результатом живёт в `counters.ban_kinds` (колонку `ban_kind` пишет только `mark_banned`) — это осознанно: такой прогон в `SELECT ban_kind` считаться баном и не должен. Стоп-кран для infra-серий и выравнивание cian — #3288 п.5.
bot-backend merged commit 7afaa12d75 into main 2026-09-05 19:38:48 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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#3367
No description provided.