gendesign/data/sql/176_objective_lots_physflat_covering_idx.sql
Light1YT 8074cb49ef
All checks were successful
CI / changes (pull_request) Successful in 8s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m14s
CI / backend-tests (pull_request) Successful in 14m23s
perf(objective_lots): inline-pushdown competitors._SOLD_COUNT_SQL + covering physflat index (#1953)
Прод EXPLAIN-охота (follow-up эпика #1953):

#1 competitors._SOLD_COUNT_SQL — фикс регрессии #1964. JOIN
v_objective_lots_latest материализовал DISTINCT ON по всей 1.76M-таблице
ДО фильтра маппинга (qual не проталкивается ниже view-DISTINCT ON) →
Parallel Seq Scan + external sort (~213 MB temp). Переписан на
inline-pushdown (зеркало #1964 / market_metrics._STOCK_SQL): фильтр
objective_complex_mapping в CTE → DISTINCT ON по СЫРОЙ objective_lots,
JOIN ON project_name (objective_lots_project_idx). Прод: 7958 → 292 ms,
вывод побайтово идентичен (15/15 obj_id, 0 mismatch). COUNT(DISTINCT
objective_lot_id) сохранён (fan-out-safety).

#2/#6/#5 миграция 176 — покрывающий physflat-индекс. EKB-wide
_STOCK_SQL/_SALES_WINDOW_SQL full-seq-scan + external sort (~140 MB temp),
т.к. partial-индекс 173 не нёс проецируемых колонок. Новый
objective_lots_physflat_covering_idx = тот же ключ + INCLUDE(rooms_int,
area_pd, sales_start_date, is_sold, contract_date, status, class) +
WHERE premise_kind='квартира'; старый _latest_idx дропнут (полностью
замещён). Прод DRY-RUN: Index Only Scan + Unique, без external sort,
5177 → 1595 ms; индекс 252 MB. ANALYZE objective_lots в конце tx.

Деплой вне недельного окна objective-scrape (кратковременный SHARE-lock
при drop+create ~252 MB индекса на 1.75M строк).
2026-06-28 18:45:47 +05:00

76 lines
4.4 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Контекст: 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;