fix(tradein/scrapers): метка наблюдения — время строки, а не старта транзакции (#2731) #2742
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#2742
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/2731-clock-timestamp-content"
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?
Что
observed_at/scraped_at/last_seen_atписалисьNOW()==transaction_timestamp(). Писатель объявлений коммитит ОДИН раз в конце batch'а, поэтому все строки вызова несли одну метку.Замер на проде 2026-08-06 (строк / различных меток):
listings_snapshots.observed_atlistings.scraped_at/last_seen_atlistings.detail_enriched_atlisting_source_snapshots.observed_atsber_price_index.fetched_atСоразмерность
Фильтры свежести и TTL не искажены: смещение равно длительности прогона (минуты; худший случай 17.3 ч у
cian_full_load) против окна 14 суток — 0.03-5%. Формулировка «долгие прогоны ломают фильтр свежести» проверена и снята.Настоящая цена — разрешение во времени: темп сбора и порядок строк внутри прогона по данным не восстановить (отсюда же следовала невозможность бэкфилла
run_id, #2701).Почему
statement_timestamp(), а неclock_timestamp()как в #2702/#2718Здесь одним statement'ом пишутся ДВЕ колонки, обязанные совпадать: после #2206
scraped_at = last_seen_atу 100% строк, и на этом равенстве стоит предикат миграции 161 (WHERE last_seen_at > scraped_atкак признак «видели живым, но не пере-скрейпили»). Проверено на проде:clock_timestamp()развёл бы колонки на микросекунды — создал бы расхождение там, где его сейчас нет.statement_timestamp()при этом двигается от запроса к запросу внутри одной транзакции (замер: 1.2 с между соседними запросами при неподвижномnow()), а каждый upsert объявления — свой запрос.Что НЕ тронуто и почему
Set-based писатели (
listing_source_snapshots— 89 827 строк однимINSERT … SELECT;deactivate_stale_avito— data-modifying CTE) оставлены наnow(): там одна метка — правда о statement'е.clock_timestamp()дал бы ложное разрешение — на продеcount(DISTINCT clock_timestamp())по 200 000 строк одного statement'а = 27 783 значения, кодирующих порядок обработки строк планировщиком, а не порядок наблюдения.detail_enriched_atне менялся: замер показал, что он НЕ схлопывается (коммит поштучный) — правка была бы холостой.Потребители, опирающиеся на схлопнутость
Не найдено.
observed_atвообще не читается ни бэкендом, ни фронтом (только писатели). Ни одногоGROUP BY/DISTINCTпо этим колонкам в коде нет. Единственное место, зависящее от ОТНОШЕНИЯ колонок, — миграция 161 (last_seen_at > scraped_at), и её инвариант сохранён выборомstatement_timestamp().Историческая граница
Миграция 232 — только
COMMENT ON COLUMN(идемпотентна, данных не трогает): фиксирует дату перехода (#2731, 2026-08-06) в комментарии каждой из трёх колонок и то, что исторические строки невосстановимы — внутрипрогонное время нигде больше не сохранялось.Test plan
tests/test_2731_observation_timestamps.py— модель БД на трёх функциях времени: писатель, отработавший дольше секунды, оставляет РАЗЛИЧНЫЕ метки у строк, иscraped_at == last_seen_atвнутри строкиNOW()) падают 3 из 5 тестов (обе поведенческие + инвариант источника)clock_timestamp()тоже падает — модель воспроизводит разброс двух вызовов в одном statement'еRefs #2731