fix(tradein): TTL-деактивация не исполняется, пока сбор по источнику лежит (#2659) #2710

Merged
bot-backend merged 1 commit from fix/2659-ttl-knows-coverage into main 2026-08-06 08:40:42 +00:00
Collaborator

Что и почему

TTL-деактивация исполнялась вслепую. Авито 10.07-26.07.2026: 17 суток подряд без единой собранной страницы, TTL=10 снял за отрезок 9 033 строки (сумма counters->>'deactivated' по прогонам, цифра из #2659 воспроизведена точно). 1 270 из них потом доказанно вернулись живыми — сверка listing_source_snapshots (кого TTL был обязан снять в те сутки) с текущим last_seen_at. Задача прочитала «мы не смогли зайти» как «объявление снято».

Почему не по статусу прогона

scrape_runs.status='banned' — ловушка, оба других провала для него невидимы:

источник окно прогонов/сут banned total_seen что сделал TTL
yandex 18.07-30.07 (13 сут) 5, все done 0 0 снял ~839 строк vtorichka
domklik 20.07-30.07 (11 сут) 1-2, все done 0 0 02.08 снял 6 131 строку разом

Поэтому здоровье меряется результатом: сколько строк источник подтвердил свежими за 3 суток — по той же колонке (last_seen_at / scraped_at) и тому же срезу source + segments, что и сам UPDATE. Одна колонка на обе стороны: гейт нельзя обмануть bulk-touch'ем, не двигающим scraped_at (#2204).

Пороги (миграция 219)

Ряд «подтверждений за 3 суток» по дням восстановлен из listing_source_snapshots:

расписание здоровые сутки провал порог
avito (все сегменты, last_seen_at) 3542..6079 0..970 1500
yandex (vtorichka) 897..2206 0 500
cian (vtorichka) 748..4329 провала не было 500
domklik (scraped_at) сейчас 62 200

Порог авито лежит строго между максимумом провала (970) и минимумом здоровых суток (3542). Цена ошибки асимметрична — пропущенная деактивация чинится следующим прогоном, ложная только повторным сбором, — поэтому пороги смещены в сторону «пропустить прогон».

domklik заблокируется сразу, и это верный исход: источник подтверждает ~50 строк в сутки, TTL по нему уже снёс инвентарь целиком 02.08. Протухшее там закрывается фильтром свежести на чтении (#2656), а не TTL.

Что НЕ трогается

Охват TTL не расширен ни на один сегмент — по порядку действий из #2659 (сначала #2656, потом TTL). Разбор новостроек Циана и несогласованности Яндекса — в комментарии к issue, отдельным PR после #2656.

Test plan

  • test_gate_blocks_every_day_of_the_17_day_avito_ban — проигрывает реальный прод-ряд подтверждений по дням: все 20 суток провала блокируются, ни одни здоровые сутки не задеты. На origin/main падает (12 из 21 теста файла падают на старом коде).
  • 88 тестов deactivate_stale (старые + новые) зелёные, ruff + ruff-format чисто
  • Прод: после деплоя deactivate_stale_domklik уходит в skipped_unhealthy=1, остальные три исполняются как раньше

Refs #2659

## Что и почему TTL-деактивация исполнялась вслепую. Авито **10.07-26.07.2026**: 17 суток подряд без единой собранной страницы, TTL=10 снял за отрезок **9 033 строки** (сумма `counters->>'deactivated'` по прогонам, цифра из #2659 воспроизведена точно). **1 270** из них потом доказанно вернулись живыми — сверка `listing_source_snapshots` (кого TTL был обязан снять в те сутки) с текущим `last_seen_at`. Задача прочитала «мы не смогли зайти» как «объявление снято». ## Почему не по статусу прогона `scrape_runs.status='banned'` — ловушка, оба других провала для него **невидимы**: | источник | окно | прогонов/сут | banned | total_seen | что сделал TTL | |---|---|---|---|---|---| | yandex | 18.07-30.07 (13 сут) | 5, все `done` | **0** | **0** | снял ~839 строк vtorichka | | domklik | 20.07-30.07 (11 сут) | 1-2, все `done` | **0** | **0** | 02.08 снял **6 131** строку разом | Поэтому здоровье меряется **результатом**: сколько строк источник подтвердил свежими за 3 суток — по той же колонке (`last_seen_at` / `scraped_at`) и тому же срезу `source + segments`, что и сам UPDATE. Одна колонка на обе стороны: гейт нельзя обмануть bulk-touch'ем, не двигающим `scraped_at` (#2204). ## Пороги (миграция 219) Ряд «подтверждений за 3 суток» по дням восстановлен из `listing_source_snapshots`: | расписание | здоровые сутки | провал | порог | |---|---|---|---| | avito (все сегменты, `last_seen_at`) | 3542..6079 | 0..970 | **1500** | | yandex (vtorichka) | 897..2206 | 0 | **500** | | cian (vtorichka) | 748..4329 | провала не было | **500** | | domklik (`scraped_at`) | — | сейчас 62 | **200** | Порог авито лежит строго между максимумом провала (970) и минимумом здоровых суток (3542). Цена ошибки асимметрична — пропущенная деактивация чинится следующим прогоном, ложная только повторным сбором, — поэтому пороги смещены в сторону «пропустить прогон». **domklik заблокируется сразу, и это верный исход**: источник подтверждает ~50 строк в сутки, TTL по нему уже снёс инвентарь целиком 02.08. Протухшее там закрывается фильтром свежести на чтении (#2656), а не TTL. ## Что НЕ трогается Охват TTL не расширен ни на один сегмент — по порядку действий из #2659 (сначала #2656, потом TTL). Разбор новостроек Циана и несогласованности Яндекса — в комментарии к issue, отдельным PR после #2656. ## Test plan - [x] `test_gate_blocks_every_day_of_the_17_day_avito_ban` — проигрывает реальный прод-ряд подтверждений по дням: все 20 суток провала блокируются, ни одни здоровые сутки не задеты. На `origin/main` падает (12 из 21 теста файла падают на старом коде). - [x] 88 тестов deactivate_stale (старые + новые) зелёные, ruff + ruff-format чисто - [ ] Прод: после деплоя `deactivate_stale_domklik` уходит в `skipped_unhealthy=1`, остальные три исполняются как раньше Refs #2659
bot-backend added 1 commit 2026-08-06 08:13:08 +00:00
fix(tradein): TTL-деактивация не исполняется, пока сбор по источнику лежит
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 8s
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 3m2s
917c911eb9
Авито 10.07-26.07.2026: 17 суток подряд без единой собранной страницы, TTL=10
снял за отрезок 9 033 строки. 1 270 из них потом вернулись живыми, как только
сбор восстановился (сверка listing_source_snapshots с текущим last_seen_at) —
задача прочитала «мы не смогли зайти» как «объявление снято».

Гейт меряет здоровье НЕ статусом прогона, а результатом: сколько строк источник
подтвердил свежими за 3 суток, по той же колонке (last_seen_at / scraped_at) и
тому же срезу source+segment, что и сам UPDATE. Статус не годится — оба других
провала для него невидимы: yandex 18.07-30.07 (13 суток, все прогоны 'done',
ноль 'banned', total_seen=0) и domklik 20.07-30.07, чей TTL 02.08 снял 6 131
строку разом.

Пороги посчитаны по ряду подтверждений из listing_source_snapshots и лежат между
максимумом провала и минимумом здоровых суток: avito 1500 (провал 0..970,
здоровье 3542..6079), yandex/cian 500, domklik 200. Миграция 219 кладёт их в
default_params, не затирая ручную настройку; страховочный дефолт в коде — 500.
Цена ошибки асимметрична (пропущенная деактивация чинится следующим прогоном,
ложная — только повторным сбором), поэтому при сомнении прогон пропускается.

Пропущенный прогон закрывается mark_done со счётчиками
{deactivated: 0, confirmations: N, skipped_unhealthy: 1} — ни одна строка не
тронута, статусы прогонов не менялись.

Тест test_gate_blocks_every_day_of_the_17_day_avito_ban проигрывает реальный
прод-ряд по дням: каждые сутки провала блокируются, здоровые — нет.

Refs #2659
bot-backend merged commit 627e163103 into main 2026-08-06 08:40:42 +00:00
bot-backend deleted branch fix/2659-ttl-knows-coverage 2026-08-06 08:40:42 +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#2710
No description provided.