76 lines
4.4 KiB
PL/PgSQL
76 lines
4.4 KiB
PL/PgSQL
-- Контекст: 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;
|