fix(tradein/deactivate-stale): пол переобхода — LATERAL вместо equality-join по дате #3056
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#3056
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/revisit-floor-lateral-lookup"
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 по переходу
listing_source_snapshotsна модель «строка на изменение» (развилка #2993, решение владельца 2026-08-23). Порядок жёсткий: читатель раньше писателя, обратный даёт зелёный деплой и обвал на четвёртые сутки.Зачем
_build_revisit_floor_sqlвычисляет минимальный TTL по парам «предыдущее наблюдение → текущее». Джойн шёл по равенству даты снимка с якорем. Это ломается двумя независимыми способами.Будущий. В change-only модели строка пишется только при изменении. На 2026-08-20 «изменениями» являются 4 458 строк из 101 795 — equality-join теряет 95.6 % пар, выборка схлопывается до n=3–4,
percentile_disc(0.99)вырождается в максимум из трёх чисел. Обвал пола по срезам: domklik 28 → 14 (под снятие сразу 128 активных строк), yandex/vtorichka 44 → 30, avito 13 → 10.Уже сейчас. Дыры в суточной истории дают
n_pairs=0 → floor_days=NULL → TTL как задан, молча. На проде такие дыры есть: 03–14.06 (двенадцать суток подряд), 03–04.07, 12.07, 26.07, 30–31.07, 01.08 — то есть пол не считался ровно в моменты разбора завалов.Что меняется
1. Равенство по дате → последняя строка не позже якоря.
JOIN LATERAL (… ORDER BY snapshot_date DESC LIMIT 1). Семантика сохраняется точно: между изменениямиlast_seen_atпо определению постоянен.2. Глобальный
max(snapshot_date)→ максимум внутри источника. Старый подзапрос брал максимум по всей таблице, не скоупленный поlisting_source_id— источник, чья история короче общей, выпадал из выборки целиком. Это отдельный фикс, а не побочный эффект, и назван здесь намеренно.3.
floor_n_pairsв counters — наблюдательность под будущий гейт деградации пола (PR-B). Сейчас ничего не блокирует.FROM..WHEREвынесен в_revisit_floor_from_where_sql, чтобыcount(*)иpercentile_discгарантированно шли по одному срезу, а не разошлись при будущей независимой правке одного из двух.Замер на живом проде, 2026-08-23
health_window_days=3, обе формы запроса рядом:Совпадение побитовое. Сегодня суточная джоба пишет строку для каждого источника каждый день, поэтому глобальный максимум совпадает с максимумом каждого источника — пункт 2 является no-op на текущих данных. Расхождение проявится только на дырах и после перехода на change-only.
Иначе говоря, на прод это выкатывается с нулевым изменением поведения, а защищает от обвала, который наступил бы при PR-C.
Тесты
Новый
tests/test_revisit_floor_lateral_lookup.py: три live-теста против настоящего Postgres и четыре чистых SQL-теста без БД.Ключевой —
test_equality_join_loses_gapped_pairs_and_gives_a_smaller_floor: на одних и тех же трёх парах (одна со снимком ровно на дату якоря, две с гэпом) LATERAL видит все три (n_pairs=3, пол 65.0), воспроизведённый старый equality-join — только одну (n_pairs=1, пол 5.5).Live-тесты используют
uuid-суффикс вsource, поэтому независимы от порядка выполнения и данных соседей; очистка — чистыйrollback(), без коммитов.Прогон: 160/160 в целевом наборе (новый файл + все пять существующих
test_deactivate_stale_*).Границы
Писатель
listing_source_snapshot.pyне тронут, миграций нет, гейты и предохранители не добавлены — это PR-B и PR-C.Refs #2993, #2659