fix(scrapers): deactivate pre-#753 ghost duplicate listings (dedup correctness) #1557
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
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#1557
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/deactivate-ghost-duplicate-listings"
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
One-shot data-correctness migration
113_deactivate_ghost_duplicate_listings.sql.Before #753,
dedup_hashincluded the price, so each price change spawned a new row with a different hash. The current scraper upserts the canonical row (ON CONFLICT (dedup_hash), hash =sha256(source||'|'||source_id)), but the old "ghost" rows keep their price-inclusive hash, are never matched again, have frozenlast_seen_at, and stayis_active=trueforever — inflating active-listing counts (estimator/coverage/asking-to-sold denominators).This migration deactivates, within each
(source, source_id)group that has >1 rows AND at least one canonical-hash row, the active rows whose hash is not canonical. Groups with no canonical row (35 tiny all-ghost groups) are excluded via theHAVINGfilter — deactivating them would wipe the whole group.Verification (prod, rolled-back transaction, 2026-06-16)
avito ghosts are already inactive (
deactivate_stale_avito); cian ghosts already mostly inactive; yandex has no deactivation yet so its ghosts are the bulk.dedup_hashhas a UNIQUE constraint, so at most one canonical row per group.Why safe
BEGIN/COMMITatomic; idempotent (AND is_active=true→ re-run is a no-op).is_active=trueON CONFLICT clause.merged_intodeliberately not set (column is dead — no code writer).Test plan
Refs #753
Deep review (post-hoc — PR was already squash-merged by lekss361 at 12:59Z; not re-merged).
Verdict: APPROVE. Migration is correct, idempotent, atomic, and verified clean on prod.
Checks against the landed file (merge commit
496af12c) and live prod DB:AND is_active=trueguard → re-run is a no-op). Confirmed.113_yandex_detail_backfill.sql: NOT a problem —_schema_migrationskeys on full filename (PK), both applied independently (11:20Z / 13:02Z). Same accepted pattern as the existing two108_*files. Minor convention smell only.sha256(source||'|'||source_id)is scoped viaWHERE source_id IS NOT NULL— exactly the subset where it equals the scraper'scompute_dedup_hash(key=source_id when present). Re-scrapes upsert the canonical row via ON CONFLICT, never the deactivated ghost. source_id-NULL (Yandex URL-fallback) rows untouched. Correct.::byteacasts fine in raw SQL migration context._schema_migrationsrow present.No blockers.