Снимки источников МЕРА пишутся только при изменении — в 26–38 раз меньше строк за ночь (#2993, PR-C) #3577
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#3577
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/snapshot-change-only"
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?
PR-C из четырёх по переходу
listing_source_snapshotsна «строку на изменение» (решение владельца 2026-08-23, развилка в #2993). Читатель уже готов: пол переобхода ищет прошлое наблюдение через LATERAL (PR-A #3056), рельсы объёма снятия стоят (PR-B #3066). Этот PR меняет писателя.Что было
_SNAPSHOT_SQLделалINSERT … SELECT … FROM listing_sourcesбезWHERE, то есть каждую ночь копировал все источники. Замер чтением на проде 17.09:11–13.09 — всплеск первых появлений (московский объём). В обычные сутки меняются 3–4 % строк, то есть писать нужно в 26–38 раз меньше. Таблица сейчас 8,1 млн строк, 1,87 ГБ.
Что сделано
Строка пишется, только если у источника ещё нет снимка или хоть одно из четырёх значений
(price_rub, is_active, last_seen_at, payload_hash)отличается от его последнего снимка:Почему именно так:
last_seen_atв сравнении обязателен. Пол переобхода (_revisit_floor_from_where_sql) берётlast_seen_atпоследнего снимка не позже якоря. Состояние на дату D = последняя строка сsnapshot_date <= D, и для всех четырёх колонок оно совпадает с тем, что записала бы суточная копия. Если убратьlast_seen_atиз сравнения, пары «прошлое наблюдение → текущее» считались бы от старой свежести. Сколько это стоит: безlast_seen_atписалось бы 1,7–4,7 тыс. строк в сутки, с ним 7,7–11,4 тыс.is_activeтоже в сравнении. Он зависит отnow(): еслиlast_seen_atне меняется, строка всё равно перестаёт быть активной через 7 суток. Это изменение состояния на дату, одна строка на источник, на проде 0,6–4 тыс. в сутки.pвключает сегодняшний снимок. Повторный прогон в те же сутки сравнивает с уже записанной сегодня строкой. Если цена вернулась к вчерашней, сегодняшняя строка перезаписывается, и на сегодня не остаётся цены, которой у источника уже нет.first_seen, ниprice_change, ниeditedу него сработать не могли. Набор событий тот же, что при суточной копии.Время с условием (EXPLAIN ANALYZE того же отбора на проде 17.09, только SELECT): 293 602 LATERAL-поиска по PK заняли 1,0 с. Бюджет
SET LOCAL statement_timeout900 с. Сейчас весь прогон идёт 7–13 с, event-diff после правки обрабатывает 3–4 % строк вместо 100 %.Миграция 325: только
COMMENT ON TABLE listing_source_snapshotsиCOMMENT ON VIEW v_listing_source_price_on_date, сlock_timeout 5s. Прежние тексты «Ежедневный снимок…» и «на дату снимка» после перехода стали бы неправдой:WHERE snapshot_date = Dтеперь отдаёт только изменившиеся в D. Читателей у view нет: по коду grep пустой, уgendesign_readerнет грантов, вpg_stat_statementsобращений нет. Схема не меняется.Проверено, кто ещё читает таблицу: на проде по
pg_dependесть только этот view, функций нет. В коде читают два места:deactivate_stale_avito._revisit_floor_from_where_sql(LATERAL, готов с PR-A) и сам писатель. Строчкаmax(snapshot_date)из разведки 21.08 ушла ещё в PR-A.Тесты
Новый
tests/test_2993_snapshot_change_only.py, три live-теста против настоящего Postgres, проверки по значениям:test_only_changed_or_new_sources_get_a_row_today. Семь источников, по одному на каждую ветку: без изменений; без изменений при снимке 5-дневной давности и более старом, отличающемся (сравнение идёт с последним); цена; хеш; толькоlast_seen_at; толькоis_active; новый. Проверяется, у кого есть строка за сегодня и с какими значениями.test_events_survive_change_only_writes: события ровноprice_change(2,0408 %),editedиfirst_seenу нужных источников.test_rerun_same_day_compares_with_todays_row: при втором прогоне в те же сутки строка перезаписывается при возврате цены, появляется при изменении после первого прогона и не трогается у неизменившегося.Прогоны (МЕРА backend, локальный postgis 16-3.4 со всеми миграциями, включая 325, применённую дважды):
test_2993_* test_listing_source_snapshot test_2674_writers_honor_schema test_revisit_floor_lateral_lookup test_migration_numbering test_3463_db_timeouts: 59 passed, rc=0;localhost:5432/test: 6370 passed, 47 skipped, 4 failed. Эти 4 падения (test_3466_corridor_tier_a×2,test_estimator_radius_floor×2,'Settings' object has no attribute 'estimate_corridor_clamp_slack') воспроизводятся на чистомorigin/maina130303cв отдельном worktree (4 failed, 6 passed). К этому PR они не относятся;ruff check app tests: passed;ruff format --check: passed.Фальсификация
Копия исходника в scratchpad, три поломки по очереди, после каждой исходник восстановлен,
diff -qчистый.WHERE, то есть вернул полную копию:last_seen_atиз сравнения:s.snapshot_date < CURRENT_DATE):Тест событий при поломке 1 остаётся зелёным, и так и должно быть: события не зависят от того, пишутся неизменившиеся строки или нет.
Приёмка на проде
Первый прогон после деплоя: окно 01:00–02:00 UTC, ближайший
next_run_at2026-09-18 01:22 UTC. Если деплой будет позже, даты сдвигаются на столько же.scrape_runsсsource='listing_source_snapshot'статусdone,counters.snapshottedпорядка 8–15 тыс., а не ~293 тыс. Та же цифра вSELECT count(*) FROM listing_source_snapshots WHERE snapshot_date = '2026-09-18'.first_seen_events316–1571,price_change_events174–607,edited_events7–66.CURRENT_DATE - 3впервые попадает в change-only период.deactivate_stale_{avito,cian,domklik,yandex}.counters.floor_n_pairsне падает больше чем в 5 раз против 14–17.09 (avito 828–2208, cian 719–2935, domklik 3285–6075, yandex 2142–2598),skipped_floor_degradedнет.Что не входит
idx_lss_source_date(363 МБ, те же колонки, что у PK). План на проде идётIndex Scan Backward using listing_source_snapshots_pkey. Удаление — отдельный PR сDROP INDEX CONCURRENTLY.Refs #2993, #2659
🤖 Generated with Claude Code