fix(tradein/ingest): rosreestr_dkp_import курсор переживает рестарт (#3168) #3176
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#3176
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/3168-backfill-cursor-resume"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Закрывает #3168 после поправки его посылки (см. комментарий в тикете): дефект не у пяти перечисленных backfill'ов, а у
import_rosreestr_dkp.Что было
backend/app/services/scheduler.py,import_rosreestr_dkp(с 162): курсорWHERE id > CAST(:last_id AS bigint) ORDER BY idидёт отlast_id, который живёт только в памяти процесса. Heartbeat пишется каждый батч, но позицию не сохраняет — комментарий на:198при этом называет heartbeat чекпоинтом.Обрыв — деплой, OOM, рестарт — и следующий запуск начинает импорт с нуля.
Что стало
scrape_runs.counters.last_id, пишется мержем черезkit_runs.update_heartbeat(counters || :counters). Локальныйruns_mod.update_heartbeatделает полную замену — на нём вердикт резюме стирался бы первым же батчем._resume_dkp_cursor. Общий_pick_resumeне переиспользован намеренно: у него другой контракт (bucket-множество,params,interval_days), а здесь курсор числовой.running,zombieиdoneс меткойcounters.interrupted = 1. Ветка SIGTERM-drain теперь помечает себя прерванной, а не мнимо-штатной. Чистыйdoneне резюмируется — иначеON CONFLICT DO UPDATEперестанет подхватывать правки старых сделок._resume_decision, с запасом перекрывает наибольший наблюдаемый разрыв heartbeat у соседних циклов (150 минут).Проверка
8 passed— resume, SIGTERM-drain, чистыйdone, отсутствие предыдущего прогона, протухший курсор, граница потолка, мерж ×2.Обратимость подтверждена: с временно откаченным
_resume_dkp_cursor—6 failed, 2 passed; после восстановления снова8 passed. ruff check и format чисто, CRLF сохранён (589/589), паттерна::typeнет.Ветка перебазирована на актуальный
main— без этого PR откатил бы #3170 и #3118, смерженные этой ночью.Refs #3168, #2989
last_id жил только в памяти процесса (import_rosreestr_dkp, scheduler.py): heartbeat писал его в scrape_runs.counters каждый батч (комментарий рядом прямо называл это чекпоинтом), но при старте last_id всегда инициализировался литералом 0 — обрыв (деплой/OOM/рестарт хоста) откатывал прогресс и заставлял пере-сканировать источник с начала. Разведка: из пяти backfill-циклов issue (avito_detail_backfill, house_imv_backfill, cian_history_backfill, yandex_detail_backfill, geocode_missing_listings) ни один не имеет этого дефекта — все устроены как WHERE ... IS NULL/NOT EXISTS ... LIMIT, естественно резюмируемы без курсора. Единственный код, буквально описанный в issue (строки/SQL/комментарий), это шестой, не входящий в таблицу backfill — rosreestr_dkp_import. Фикс — _resume_dkp_cursor(db, run_id): - кандидат — последний прогон source='rosreestr_dkp_import'; - резюмится только незавершённый штатно прогон: status running/zombie, либо done с counters.interrupted=1 (SIGTERM-drain — эта ветка раньше считала последующий full rescan штатным поведением, теперь помечает себя как прерванную и резюмится наравне с zombie); - потолок возраста чекпоинта — 24ч, старше — 'checkpoint_stale', старт с 0; - чистый 'done' (полный проход) не резюмится — иначе ON CONFLICT DO UPDATE перестанет ловить правки уже импортированных сделок при следующем проходе. Вердикт и per-batch чекпоинт пишутся через kit_runs.update_heartbeat (merge `counters || :counters`) вместо локального runs_mod.update_heartbeat (полная замена) — иначе resume-вердикт стирался первым же heartbeat'ом батча. Тесты: tests/test_3168_backfill_cursor_resume.py — резюм с сохранённого last_id, резюм после SIGTERM-drain, отказ резюмить чистый done, отказ резюмить протухший (>24ч) чекпоинт, merge не стирает посторонние ключи. Обратимость проверена вручную (временный откат _resume_dkp_cursor красил 6 из 8 тестов).