feat(tradein/scraper): чекпоинты для avito_newbuilding_sweep — страница как единица (#3074) #3169

Merged
bot-backend merged 1 commit from feat/3074-newbuilding-sweep-resume into main 2026-08-27 21:31:53 +00:00
Collaborator

Закрывает остаток по чекпоинтам свипов из #3074: avito_newbuilding_sweep был последним длинным свипом без resume_run_id.

Что было

save_listings вызывался один раз на весь sweep. Обрыв на любой странице — и собранное терялось целиком, следующий прогон начинал с первой страницы.

Что стало

Единица возобновления — страница выдачи, по образцу якорей в city sweep. _paginate_sweep / fetch_newbuildings получили start_page (уже собранные страницы не запрашиваются) и колбэк on_page, вызываемый только после того, как страница пройдена до конца. Номера пройденных страниц копятся в scrape_runs.counters.done_buckets мержем через update_heartbeat — новых записей в БД не появляется.

Два инварианта — правки после ревью

Первая версия нарушала оба; обе находки пришли из ревью и закрыты в этом же коммите.

1. В чекпоинт — только страница, чьи лоты сохранены. Отметка стояла до save_listings, а исключение из save перехватывалось и не пробрасывалось. Страница с упавшим сохранением уходила в чекпоинт → следующий прогон пропускал её навсегда, молча: прогон завершался штатно, просто часть выдачи не собиралась никогда. Это ровно тот сценарий, ради которого затевался #3074.

2. Подхват — с первой несобранной страницы, а не с max+1. Дыра в чекпоинте возможна именно из-за п.1, и max(done)+1 перепрыгнул бы её навсегда — дефект просто переезжал бы на шаг дальше. Теперь страницы после дыры перечитываются: это дешевле потери и безопасно, повторная запись схлопывается по dedup_hash.

Проверка

  • 52 passedtest_3074_avito_newbuilding_checkpoint.py, test_scraper_kit_pipeline_parity2.py, test_avito_newbuilding_sweep.py.
  • Два новых теста на инварианты проверены на обратимость: с возвращённым дефектом оба падают (страница с упавшим save попала в чекпоинт, подхват начался не с дыры), с фиксом проходят.
  • ruff check / ruff format — чисто.

Приоритет

По замеру от 27.08 (heartbeat_at - started_at, а не finished_at, который у прерванных прогонов ставит финализатор часами позже) этот свип живёт максимум 27 минут при среднем 8 — то есть это завершение покрытия, а не спасение дорогих прогонов. Дорогие прогоны без чекпоинтов — курсорные backfill-циклы, вынесены в #3168.

Отдельно

Тот же дефект инварианта найден в _on_combo у run_yandex_city_sweep — он уже в main и работает на проде. В этот PR не тащу, завёл отдельно.

Refs #3074, #2989

Закрывает остаток по чекпоинтам свипов из #3074: `avito_newbuilding_sweep` был последним длинным свипом без `resume_run_id`. ## Что было `save_listings` вызывался **один раз на весь sweep**. Обрыв на любой странице — и собранное терялось целиком, следующий прогон начинал с первой страницы. ## Что стало Единица возобновления — страница выдачи, по образцу якорей в city sweep. `_paginate_sweep` / `fetch_newbuildings` получили `start_page` (уже собранные страницы не запрашиваются) и колбэк `on_page`, вызываемый только после того, как страница пройдена до конца. Номера пройденных страниц копятся в `scrape_runs.counters.done_buckets` мержем через `update_heartbeat` — новых записей в БД не появляется. ## Два инварианта — правки после ревью Первая версия нарушала оба; обе находки пришли из ревью и закрыты в этом же коммите. **1. В чекпоинт — только страница, чьи лоты сохранены.** Отметка стояла до `save_listings`, а исключение из save перехватывалось и не пробрасывалось. Страница с упавшим сохранением уходила в чекпоинт → следующий прогон пропускал её навсегда, молча: прогон завершался штатно, просто часть выдачи не собиралась никогда. Это ровно тот сценарий, ради которого затевался #3074. **2. Подхват — с первой несобранной страницы, а не с `max+1`.** Дыра в чекпоинте возможна именно из-за п.1, и `max(done)+1` перепрыгнул бы её навсегда — дефект просто переезжал бы на шаг дальше. Теперь страницы после дыры перечитываются: это дешевле потери и безопасно, повторная запись схлопывается по `dedup_hash`. ## Проверка - `52 passed` — `test_3074_avito_newbuilding_checkpoint.py`, `test_scraper_kit_pipeline_parity2.py`, `test_avito_newbuilding_sweep.py`. - Два новых теста на инварианты проверены на обратимость: с возвращённым дефектом оба падают (`страница с упавшим save попала в чекпоинт`, `подхват начался не с дыры`), с фиксом проходят. - `ruff check` / `ruff format` — чисто. ## Приоритет По замеру от 27.08 (`heartbeat_at - started_at`, а не `finished_at`, который у прерванных прогонов ставит финализатор часами позже) этот свип живёт максимум **27 минут** при среднем 8 — то есть это завершение покрытия, а не спасение дорогих прогонов. Дорогие прогоны без чекпоинтов — курсорные backfill-циклы, вынесены в #3168. ## Отдельно Тот же дефект инварианта найден в `_on_combo` у `run_yandex_city_sweep` — он уже в `main` и работает на проде. В этот PR не тащу, завёл отдельно. Refs #3074, #2989
bot-backend added 1 commit 2026-08-27 21:12:38 +00:00
feat(tradein/scraper): чекпоинты для avito_newbuilding_sweep — страница как единица (#3074)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
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 / openapi-codegen-check (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m41s
5a410687ac
Последний длинный свип без возобновления: при обрыве прогон начинался с
первой страницы, а собранное терялось целиком — save_listings вызывался
один раз на весь sweep.

Единица возобновления — страница выдачи, по образцу якорей в city sweep.
`_paginate_sweep`/`fetch_newbuildings` получили `start_page` (уже собранные
страницы не запрашиваются) и колбэк `on_page`, который вызывается только
после того, как страница пройдена до конца. Сохранение стало постраничным,
номера пройденных страниц копятся в `scrape_runs.counters.done_buckets`
мержем через `update_heartbeat`.

Два инварианта, без которых фича вредна:

1. В чекпоинт попадает только страница, чьи лоты СОХРАНЕНЫ. Отказ
   save_listings перехвачен и прогон продолжается, но отметить такую
   страницу пройденной значило бы, что следующий прогон её пропустит и
   объявления оттуда не соберутся никогда — молча, потому что прогон
   завершится штатно.

2. Подхват начинается с ПЕРВОЙ несобранной страницы, а не с max+1. Дыра в
   чекпоинте возможна ровно из-за п.1, и max+1 перепрыгнул бы её навсегда.
   Страницы после дыры перечитаются — это дешевле потери и безопасно,
   повторная запись схлопывается по dedup_hash.

Оба инварианта закрыты тестами, которые падают при их нарушении.
bot-backend merged commit 55e13fd92d into main 2026-08-27 21:31:53 +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#3169
No description provided.