feat(tradein): checkpoint/resume для *-full-load скраперов — переживать рестарт/деплой #930

Closed
opened 2026-05-31 20:52:25 +00:00 by bot-backend · 3 comments
Collaborator

Проблема

run_cian_full_load (exhaustive room×price, #926/#929) — это in-memory asyncio-таск. При рестарте контейнера (деплой) таск убивается, позиция в room×price walk теряется. Повторный запуск идёт с нуля (room1, цена 0…) и заново перепроходит ВСЕ бакеты. Дублей в БД нет (save_listings ON CONFLICT), но ~час скрапинга переделывается впустую + лишняя нагрузка на Cian/прокси.

cian_history_backfill резюмируется естественно (WHERE history IS NULL), а у нового full-load такого нет.

Решение

Чекпойнт по завершённым leaf-бакетам:

  • Каждый завершённый ценовой бакет = ключ "{room}:{lo}:{hi}".
  • Накапливать множество завершённых ключей в scrape_runs.counters.done_buckets (jsonb), дописывать в heartbeat после каждого on_bucket. Объём ~150 ключей на полный прогон — мелочь.
  • Endpoint принимает resume_run_id: int | None. При resume новый run читает done_buckets прошлого run'а, сидит ими skip-set, и fetch_all_secondary пропускает пагинацию уже завершённых бакетов (skip по точному (room,lo,hi) ключу).
  • Граничный случай: если totalOffers сдвинулся между прогонами → границы split'а могут чуть отличаться → часть бакетов пере-фетчится (безвредно, ON CONFLICT). Common case (те же данные) — скипается корректно.

Примечание: probe-запросы (totalOffers для split-решений) на resume всё равно отрабатывают (их мало, ~1 на узел split). Скипается тяжёлая ЧАСТЬ — пагинация (>90% запросов).

Scope

  • Cian (fetch_all_secondary/_walk_price_range/run_cian_full_load/cian-full-load endpoint) — этот PR.
  • Yandex (run_yandex_full_load) — заложить ту же схему сразу при реализации Yandex exhaustive (отдельный PR в работе). Чекпойнт-логика должна быть зеркальной.

Acceptance

  • done_buckets пишется в counters per-bucket.
  • POST /scrape/cian-full-load {resume_run_id: N} → пропускает завершённые бакеты прошлого run'а (видно в логах "skip bucket X — already done").
  • Без resume_run_id — поведение прежнее (full walk).
  • Резюме самого resumed-run работает (его done_buckets = prior ∪ new).
  • Юнит-тест: skip_buckets реально пропускает пагинацию.

scope/backend.

## Проблема `run_cian_full_load` (exhaustive room×price, #926/#929) — это in-memory asyncio-таск. При рестарте контейнера (деплой) таск убивается, позиция в room×price walk теряется. Повторный запуск идёт **с нуля** (room1, цена 0…) и заново перепроходит ВСЕ бакеты. Дублей в БД нет (`save_listings` ON CONFLICT), но ~час скрапинга переделывается впустую + лишняя нагрузка на Cian/прокси. `cian_history_backfill` резюмируется естественно (`WHERE history IS NULL`), а у нового full-load такого нет. ## Решение Чекпойнт по завершённым leaf-бакетам: - Каждый завершённый ценовой бакет = ключ `"{room}:{lo}:{hi}"`. - Накапливать множество завершённых ключей в `scrape_runs.counters.done_buckets` (jsonb), дописывать в heartbeat после каждого `on_bucket`. Объём ~150 ключей на полный прогон — мелочь. - Endpoint принимает `resume_run_id: int | None`. При resume новый run читает `done_buckets` прошлого run'а, сидит ими skip-set, и `fetch_all_secondary` **пропускает пагинацию** уже завершённых бакетов (skip по точному `(room,lo,hi)` ключу). - Граничный случай: если totalOffers сдвинулся между прогонами → границы split'а могут чуть отличаться → часть бакетов пере-фетчится (безвредно, ON CONFLICT). Common case (те же данные) — скипается корректно. Примечание: probe-запросы (totalOffers для split-решений) на resume всё равно отрабатывают (их мало, ~1 на узел split). Скипается тяжёлая ЧАСТЬ — пагинация (>90% запросов). ## Scope - **Cian** (`fetch_all_secondary`/`_walk_price_range`/`run_cian_full_load`/`cian-full-load` endpoint) — этот PR. - **Yandex** (`run_yandex_full_load`) — заложить ту же схему сразу при реализации Yandex exhaustive (отдельный PR в работе). Чекпойнт-логика должна быть зеркальной. ## Acceptance - [ ] `done_buckets` пишется в counters per-bucket. - [ ] `POST /scrape/cian-full-load {resume_run_id: N}` → пропускает завершённые бакеты прошлого run'а (видно в логах "skip bucket X — already done"). - [ ] Без `resume_run_id` — поведение прежнее (full walk). - [ ] Резюме самого resumed-run работает (его done_buckets = prior ∪ new). - [ ] Юнит-тест: skip_buckets реально пропускает пагинацию. scope/backend.
lekss361 added the
status/qa
label 2026-06-06 18:43:21 +00:00
Owner

Status actualize (06.06): обе части влиты в main — cian #932 (846c501) + yandex #937 (d93e96f). Без статуса → status/qa.

Status actualize (06.06): обе части влиты в `main` — cian #932 (`846c501`) + yandex #937 (`d93e96f`). Без статуса → `status/qa`.
Author
Collaborator

qa note: код checkpoint/resume (PR #932 cian + #937 yandex) merged в prod. Runtime-verify resume через рестарт пока НЕ возможен — tradein-browser крашлупит (3141×, прокси mproxy.site down → camoufox InvalidProxy), full-load скраперы не могут стартовать. После восстановления browser+proxy → прогнать full-load, убить контейнер на середине, подтвердить resume с чекпойнта. Оставляю open до runtime-qa.

qa note: код checkpoint/resume (PR #932 cian + #937 yandex) merged в prod. Runtime-verify resume через рестарт пока НЕ возможен — `tradein-browser` крашлупит (3141×, прокси `mproxy.site` down → camoufox InvalidProxy), full-load скраперы не могут стартовать. После восстановления browser+proxy → прогнать full-load, убить контейнер на середине, подтвердить resume с чекпойнта. Оставляю open до runtime-qa.
Author
Collaborator

Закрыто — checkpoint/resume реализован: resume_run_id + done_buckets в scrape_pipeline.py (14 ссылок), *-full-load скраперы переживают рестарт/деплой через resume прерванного прогона.

✅ Закрыто — checkpoint/resume реализован: `resume_run_id` + `done_buckets` в `scrape_pipeline.py` (14 ссылок), *-full-load скраперы переживают рестарт/деплой через resume прерванного прогона.
Sign in to join this conversation.
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#930
No description provided.