perf(tradein): GIST index on (geom::geography) for estimator radius filter (#1189)
All checks were successful
CI / changes (pull_request) Successful in 6s
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

Estimator analog query casts geom::geography for ST_DWithin, but listings_geom_idx
is gist(geom) → spatial predicate fell to Filter, geom index used only for
geom IS NOT NULL (54.5k-row scan, 180ms). Add gist((geom::geography)) expression
index → ST_DWithin uses it as Index Cond. Prod dry-run (BEGIN/CREATE/EXPLAIN/ROLLBACK):
180ms → 38ms (~4.7x). Additive, idempotent (IF NOT EXISTS), non-destructive.
This commit is contained in:
Light1YT 2026-06-26 22:09:01 +05:00
parent d111573a4b
commit 57456b5d10

View file

@ -0,0 +1,36 @@
-- 134_listings_geom_geography_gist.sql
-- GIST expression-индекс на (geom::geography) — ускоряет radius-фильтр эстиматора.
--
-- WHY (#1189 — EXPLAIN-решение об индексе):
-- Эстиматор тянет аналоги через `ST_DWithin(geom::geography, point, radius)`
-- (Tier W/H/S radius-путь). Существующий `listings_geom_idx` = `gist(geom)` —
-- на geometry, а предикат кастует в geography, поэтому планировщик НЕ может
-- использовать его для пространственного условия: ST_DWithin падает в Filter, а
-- geom-индекс отрабатывает только `geom IS NOT NULL` (скан ~54.5k строк).
-- Прод-EXPLAIN (2026-06-26, ЕКБ-точка r=1500, 2-комн 45-65м²):
-- было — BitmapAnd(geom IS NOT NULL=54531) + Filter ST_DWithin → 180 мс, read 3961.
-- стало — Index Scan по (geom::geography) && _st_expand(point,1500) → ~38 мс
-- (~4.7× быстрее, тронуто ~5k строк вместо 54.5k). Замерено dry-run'ом
-- (BEGIN; CREATE INDEX; EXPLAIN ANALYZE; ROLLBACK) на реальном проде.
--
-- WHAT:
-- GIST-индекс по выражению (geom::geography). Согласован с тем, как эстиматор
-- уже пишет предикат — без правок кода: тот же `ST_DWithin(geom::geography, ...)`
-- начинает использовать индекс как Index Cond.
--
-- SAFETY:
-- - НЕ деструктивно: только добавление индекса, данные не трогаются.
-- - IF NOT EXISTS → идемпотентно при повторном прогоне (no-op).
-- - Сборка GIST по ~54.5k non-null geom строк — быстро (~1-2с), кратковременный
-- ACCESS EXCLUSIVE lock на время сборки. CONCURRENTLY не используем: раннер
-- миграций оборачивает файл в транзакцию (CREATE INDEX CONCURRENTLY в txn нельзя),
-- а на таком объёме обычная сборка дёшева.
-- - listings_geom_idx (gist(geom)) ОСТАВЛЯЕМ — он нужен другим geometry-путям.
--
-- DEPENDENCIES:
-- - listings.geom geometry(Point,4326) -- 011_listings_alter.sql / PostGIS
--
-- Deploy order: after 133_listings_uq_source_source_id.sql.
CREATE INDEX IF NOT EXISTS listings_geom_geog_idx
ON listings USING gist ((geom::geography));