-- Контекст: perf-батч после prod-EXPLAIN-охоты (follow-up эпика #1953, пункты #2/#6/#5). -- -- #2/#6 — делаем physflat-индекс ПОКРЫВАЮЩИМ, дропаем старый partial. -- EKB-wide дефолт market_metrics._STOCK_SQL (has_district=F/has_obj_ids=F) и -- зеркальный _SALES_WINDOW_SQL дедуплицируют objective_lots до последнего снапшота -- на физлот через -- SELECT DISTINCT ON (project_name, corpus_name, section, floor, lot_number) … -- ORDER BY …, snapshot_date DESC, id DESC -- по квартирам (premise_kind='квартира'). Partial-индекс миграции 173 -- (objective_lots_physflat_latest_idx) совпадал с ключом DISTINCT ON, НО НЕ нёс -- проецируемых колонок → Index-Only невозможен. Планировщик брал Parallel Seq Scan -- + external merge Sort всех 1.75M квартир-строк (прод-EXPLAIN 2026-06-28: -- ~5177 ms, temp-spill ~140 MB на трёх воркерах) вместо чтения из индекса. -- -- Покрывающий индекс добавляет INCLUDE с колонками, которые проецируют/агрегируют -- ОБА запроса ПОСЛЕ DISTINCT ON: -- _STOCK_SQL: rooms_int, area_pd, sales_start_date + sold_now-предикат -- (is_sold, contract_date, status) -- _SALES_WINDOW_SQL: rooms_int, area_pd, contract_date, class -- Объединение INCLUDE = {rooms_int, area_pd, sales_start_date, is_sold, -- contract_date, status, class}. Ключ (physflat + snapshot_date DESC, id DESC) и -- partial-WHERE (premise_kind='квартира') не меняются → тот же DISTINCT ON, но -- теперь Index-Only Scan + Unique БЕЗ Sort. -- Прод DRY-RUN (BEGIN; CREATE covering; EXPLAIN ANALYZE EKB-wide _STOCK_SQL; -- ROLLBACK): Index Only Scan using objective_lots_physflat_covering_idx + Unique, -- внешней сортировки НЕТ, Execution ~1642 ms (vs ~5177 ms). Размер ~252 MB. -- -- price_per_m2_rub в INCLUDE НЕ кладём: его проецируют только analyze-inline -- запросы (concepts._OBJECTIVE_MEDIAN_SQL, parcels district/geo-median), а они -- НЕ фильтруют premise_kind='квартира' → partial-индекс к ним и так неприменим -- (как и старый 173). Их обслуживают idx_objective_lots_pname_pricing / district- -- /geo-пути — не регрессируют (никогда не использовали partial physflat-индекс). -- -- Старый partial objective_lots_physflat_latest_idx ДРОПАЕМ: покрывающий индекс -- его полностью замещает (тот же ключ + WHERE) и обслуживает все DISTINCT ON- -- потребители (#1959 inline _STOCK/_SALES, competitors._SOLD_COUNT_SQL inline, -- analyze-inline по квартирам). Держать оба — лишний write-amp на bulk objective- -- scrape. -- -- Порядок: CREATE covering → DROP old (covering уже на месте, ни одного окна без -- индекса под DISTINCT ON). Idempotent (IF NOT EXISTS / IF EXISTS), BEGIN/COMMIT. -- -- #5 — статистика objective_lots устаревала (last_analyze был 2026-05-11, planner -- оценивал ~880 строк при реальных ~603k → плохие планы). ANALYZE в конце tx -- (выполняется в транзакции штатно) освежает оценки под новый индекс. -- -- ⚠ Деплой ВНЕ недельного окна objective-scrape: миграция дропает+создаёт ~252 MB -- индекс на 1.75M строк (кратковременный SHARE-lock на objective_lots). BEGIN; CREATE INDEX IF NOT EXISTS objective_lots_physflat_covering_idx ON objective_lots ( project_name, corpus_name, section, floor, lot_number, snapshot_date DESC, id DESC ) INCLUDE ( rooms_int, area_pd, sales_start_date, is_sold, contract_date, status, class ) WHERE premise_kind = 'квартира'; DROP INDEX IF EXISTS objective_lots_physflat_latest_idx; ANALYZE objective_lots; COMMIT;