-- 168_complexes_makepoint_geog_idx.sql -- Функциональный geography-GIST на complexes для гео-радиусной калибровки цены в /analyze. -- Ветка: feat/geo-radius-price-calibration. -- -- КОРНЕВАЯ ПРИЧИНА: новый блок «гео-радиусная цена» (parcels.py:2705, секция 10d-pre1b) -- считает медиану price_per_m2_rub новостроек Objective в 3км вокруг центроида участка. -- У objective_lots нет geom → коорды берутся через complexes (latitude/longitude), -- join по ol.complex_id = c.id. Предикат — точный метровый ST_DWithin по geography: -- -- ST_DWithin( -- ST_SetSRID(ST_MakePoint(c.longitude, c.latitude), 4326)::geography, -- ST_SetSRID(ST_MakePoint(:lon, :lat), 4326)::geography, -- 3000) -- -- Существующий complexes_geom_idx — ПЛОСКИЙ geom-GIST (geometry, SRID 4326). Планировщик -- НЕ может им воспользоваться для geography-выражения над longitude/latitude → Seq Scan -- на complexes на КАЖДЫЙ /analyze (prod EXPLAIN ANALYZE подтвердил). Это тот же урок, -- что в 157 (#157): для geography-предиката нужен функциональный GIST на ИМЕННО том -- выражении, которое стоит в WHERE. Здесь выражение — не `geom::geography`, а -- `ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)::geography`, поэтому индекс -- строится на нём байт-в-байт (планировщик матчит индекс по точному выражению). -- -- ИЗМЕРЕНИЯ (prod, EKB-центр lon=60.605 lat=56.838, radius 3000м; «после» — индекс -- создан в BEGIN…ROLLBACK для проверки плана): -- ДО: Seq Scan on complexes, ~111 ms -- ПОСЛЕ: Index Scan на (ST_SetSRID(ST_MakePoint(longitude,latitude),4326)::geography), -- ~53 ms (≈2x). Снимает Seq-Scan-нагрузку с /analyze (timeout-давление, #1850). -- -- Плоский complexes_geom_idx НЕ трогаем — он остаётся для geometry-веток (ST_Intersects -- и т.п.). Этот индекс — дополнительный, под конкретный geography-предикат. -- -- WHERE-условие частичного индекса (latitude/longitude IS NOT NULL) повторяет фильтр -- запроса (c.latitude/c.longitude IS NOT NULL) → индекс не раздувается строками без -- координат и планировщик его подбирает под тот же предикат. -- -- LOCK-ПРОФИЛЬ: обычный (НЕ CONCURRENTLY) CREATE INDEX берёт SHARE на complexes — -- блокирует только writes на миллисекунды. complexes крошечная (~2023 строки), build -- мгновенный. CONCURRENTLY здесь НЕВОЗМОЖЕН: deploy.yml гоняет .sql в транзакции -- (BEGIN/COMMIT), а CREATE INDEX CONCURRENTLY в tx запрещён. Тот же класс риска, что 157. -- -- Dependencies: complexes (latitude, longitude, id), objective_lots (complex_id) — обе -- существуют. PostgreSQL 16 + PostGIS 3.4. -- Idempotent: CREATE INDEX IF NOT EXISTS (re-apply безопасен). -- Applied: автоматически через deploy.yml в NN-порядке (трекер _schema_migrations). -- Backend code change НЕ требуется — индекс прозрачен для существующего запроса. BEGIN; CREATE INDEX IF NOT EXISTS idx_complexes_makepoint_geog ON complexes USING gist ((ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)::geography)) WHERE latitude IS NOT NULL AND longitude IS NOT NULL; COMMENT ON INDEX idx_complexes_makepoint_geog IS 'Geography GIST для гео-радиусной калибровки цены в /analyze (#168, ветка ' 'feat/geo-radius-price-calibration): ST_DWithin(' 'ST_SetSRID(ST_MakePoint(longitude,latitude),4326)::geography, …, 3000м). ' 'Плоский complexes_geom_idx не обслуживает это geography-выражение → был Seq Scan ' '~111мс, стал Index Scan ~53мс. Тот же урок, что 157. См. parcels.py:2705.'; COMMIT;