fix(tradein/deactivate-stale): пол переобхода — LATERAL вместо equality-join по дате #3056

Merged
lekss361 merged 1 commit from fix/revisit-floor-lateral-lookup into main 2026-08-23 14:37:24 +00:00

1 commit

Author SHA1 Message Date
bot-backend
fb38d657ad fix(tradein/deactivate-stale): пол переобхода — LATERAL вместо equality-join по дате
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m41s
Правка меняет две вещи разом, обе намеренно.

1. Равенство по дате -> «последняя строка не позже якоря».
   listing_source_snapshots переходит на модель «строка на изменение».
   В ней equality-join теряет 95.6% пар (на 2026-08-20 изменениями являются
   4458 строк из 101795): выборка для percentile_disc схлопывается до n=3-4,
   квантиль вырождается в максимум из трёх чисел, TTL обваливается —
   domklik 28->14 (под снятие сразу 128 активных строк), yandex 44->30,
   avito 13->10.
   Дыры в суточной истории ломали equality-join и до перехода: на проде
   03-14.06 (12 суток подряд), 03-04.07, 12.07, 26.07, 30-31.07, 01.08 —
   там n_pairs=0, floor_days=NULL и TTL молча оставался как задан.

2. Глобальный max(snapshot_date) -> максимум внутри источника.
   Старый подзапрос брал максимум по ВСЕЙ таблице, не скоупленный по
   listing_source_id: источник, чья история короче общей, выпадал из
   выборки целиком. LATERAL ищет предшественника по строке.

Замер на живом проде 2026-08-23 (health_window_days=3): обе формы дают
побитово одинаковый результат на всех четырёх источниках —
avito n=765 пол=49.7, cian n=1226 пол=81.6, domklik n=565 пол=35.7,
yandex n=3856 пол=87.3. Сегодня суточная джоба пишет строку для каждого
источника каждый день, поэтому глобальный максимум совпадает с максимумом
каждого источника, и пункт 2 — no-op на текущих данных. Расхождение
проявится только на дырах и после перехода на change-only.

Плюс floor_n_pairs в counters — наблюдательность для будущего гейта
деградации пола, сейчас ничего не блокирует.

FROM..WHERE вынесен в _revisit_floor_from_where_sql, чтобы count(*) и
percentile_disc гарантированно шли по одному срезу.
2026-08-23 15:45:53 +03:00