-- 270_houses_geog_gist_idx.sql -- #2997: у houses нет индекса под geography. Весь матчинг ходит через -- ST_DWithin(geom::geography, …) (app/services/matching/houses.py:300, :352, :554), -- а живой houses_geom_idx построен по geometry. Планировщик берёт его только как -- bitmap по `geom IS NOT NULL` и дальше фильтрует все ~9.4k строк с координатами. -- Замер на проде 21.08.2026 (EXPLAIN ANALYZE, точка в центре ЕКБ, 150 м): -- Bitmap Index Scan on houses_geom_idx, Index Cond: (geom IS NOT NULL) -- Rows Removed by Filter: 4718 на воркер, Execution Time: 70.7 ms -- pg_stat_statements за 13 ч: 66 вызовов × 39.9 мс и 5 × 53.3 мс на этот класс запросов. -- -- Функциональный GiST по (geom::geography) совпадает с выражением в запросах -- (h.geom::geography ≡ (geom)::geography), и ST_DWithin на geography начинает -- идти по индексу. houses_geom_idx НЕ трогаем: его дроп — отдельное решение по -- эпику #2989 (см. #2997, там же — почему дубль на listings тоже пока не снят). -- -- CONCURRENTLY и без BEGIN/COMMIT — по образцу 225: раннер деплоя исполняет файл -- через psql autocommit по операторам; CIC нельзя внутри транзакции, а -- lock_timeout ему вреден (гейт scripts/check-migration-lock-timeout.py -- CONCURRENTLY-формы не требует и не терпит). Таблица маленькая (10 111 строк), -- но её читает матчинг каждого объявления — ACCESS EXCLUSIVE даже на секунду не -- нужен. Если CIC оборвётся, останется INVALID-индекс — его ловит шаг проверки -- невалидных индексов в deploy-tradein.yml, и деплой краснеет, а не молчит. CREATE INDEX CONCURRENTLY IF NOT EXISTS houses_geog_gist_idx ON houses USING gist ((geom::geography)); COMMENT ON INDEX houses_geog_gist_idx IS '#2997: GiST по (geom::geography) под ST_DWithin(geom::geography, …) матчинга (matching/houses.py). До него планировщик шёл через houses_geom_idx как bitmap по geom IS NOT NULL и фильтровал все строки (~70 мс на запрос, прод 21.08.2026).';