fix(tradein/scraper): дневной снимок узнаёт свой прогон (#2701) #2707
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#2707
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/2701-snapshot-run-id"
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?
Summary
Три из девяти вызовов
save_listingsвorchestration/pipeline.pyне передавалиrun_id. У двух он лежал в той же функции — передан:run_avito_city_sweep:1113run_avito_newbuilding_sweep:1754Это тот же
run_id, под которым идёт прогон: keyword-параметр функции, по нему же зовутсяruns.is_cancelled / update_heartbeat / mark_done / mark_bannedи он печатается в каждой лог-строке sweep'а.Решение по
run_avito_pipeline:570— не передавать, и вот почемуЗаписано комментарием в коде. У функции нет строки в
scrape_runs, и продуктовых вызывающих у неё тоже нет:grep -rn "run_avito_pipeline("по репозиторию даёт только определение; все ссылки — тесты (test_pipeline_browser_routing.pyпрямо называет её legacy) и докстринги. Развёртки идут черезrun_avito_city_sweep/run_avito_newbuilding_sweep/run_avito_full_load, которые прогон заводят. Привязывать снимок не к чему; снимок сrun_idнесуществующего прогона хуже пустого поля.Писатели вне pipeline.py — проверены, помечены
backend/app/api/v1/admin.py:235scrape_runsне заводит → NULL честен, помечено комментариемbackend/scripts/ingest_domclick_jsonl.py:193backend/app/tasks/avito_detail_backfill.py:457run_idСВОЕГО прогона уже передаёт — нормproviders/cian/detail.py:382(save_detail_enrichment)run_id=None; вызывающие — backfill-задачи / admin / price-history, прогон в области видимости не у всех. НЕ трогал: доля cian 98.8%, это не дыра. Отдельная находкаОпровергнутая предпосылка issue
«Все три — Авито, и это объясняет масштаб» — не подтверждается. Заполнение по источникам:
Главная дыра — исторический domklik-ingest (см. таблицу выше), а не Авито. Но она историческая: ежедневный
run_domclick_city_sweeprun_idпередаёт (59/59 за 2026-08-05). А вот avito-дыра текущая — каждый день: 944 строки за 2026-08-06, 635 за 2026-08-05, 582 за 2026-08-04 — все с пустымrun_id. Именно её и чинит этот PR.Бэкфилл 150 415 строк — невозможен, проверено
avito_city_sweep,avito_full_load,avito_newbuilding_sweep,avito_city_sweep_<город>,avito_detail_backfill).observed_atне спасает:ON CONFLICT ... DO UPDATE SET observed_at = EXCLUDED.observed_at— метка принадлежит ПОСЛЕДНЕМУ писателю дня, а не тому, кто строку создал.Ложная атрибуция хуже пустоты — бэкфилл не делается.
Test plan
test_avito_city_sweep_passes_run_id_to_save_listings(оба anchor'а),test_avito_newbuilding_sweep_passes_run_id_to_save_listingsrun_id=run_idоба теста падают (проверено локально), с правкой — проходятsave_listings→upsert_listing_snapshot) уже под замком:test_snapshot_writer.py::test_save_listings_snapshot_receives_run_idavito_city_sweep→ доля непустыхrun_idсреди новых строк = 100%Refs #2701
Из девяти вызовов save_listings в pipeline.py три не передавали run_id. У двух — run_avito_city_sweep:1113 и run_avito_newbuilding_sweep:1754 — run_id лежал в той же функции (keyword-параметр, под которым идёт runs.mark_done/heartbeat/is_cancelled) и просто не доезжал до вызова. Передан. run_avito_pipeline:570 — оставлен без run_id осознанно, решение записано в код: у функции нет строки в scrape_runs, продуктовых вызывающих нет (единственные — тесты), привязывать снимок не к чему. Писатели вне pipeline.py проверены и тоже помечены: admin /scrape (ручной скрейп без прогона) и scripts/ingest_domclick_jsonl.py (разовый ingest файла). Второй и есть главная дыра замера — 129 908 снимков domklik без run_id, а вовсе не Авито: по источникам заполнение yandex 100%, cian 98.8%, avito 80.3%, domklik 2.9%. Дыра domklik историческая (ежедневный run_domclick_city_sweep run_id передаёт), а вот у avito пустые строки приходят КАЖДЫЙ день (944 за 2026-08-06, 635 за 2026-08-05 — все без run_id). Бэкфилл 150 515 исторических строк невозможен, проверено: - привязки снимок→прогон в схеме больше нет; - по времени неоднозначно: на всех 36 днях с осиротевшими avito-снимками в сутках было >1 пишущего avito-прогона (до 29); - observed_at не спасает — ON CONFLICT перезаписывает его последним писателем дня, а не тем, кто строку создал. Ложная атрибуция хуже пустоты — бэкфилл не делается. Refs #2701save_listingsиз девяти теряютrun_id#2701