gendesign/data/sql/153_objective_lots_district_price_idx.sql
Light1YT 11aa011d11
All checks were successful
CI / changes (push) Successful in 7s
CI / changes (pull_request) Successful in 6s
CI / frontend-tests (push) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (push) Successful in 2m3s
CI / openapi-codegen-check (pull_request) Successful in 2m7s
CI / backend-tests (push) Successful in 8m55s
CI / backend-tests (pull_request) Successful in 8m58s
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 29s
Deploy / build-worker (push) Successful in 29s
Deploy / deploy (push) Successful in 1m10s
perf(db): covering-index objective_lots district-price (#70) + 152-ФЗ/perf audit docs (#70 #79)
#70: EXPLAIN top-5 hot-path на prod — analyze district-price block по objective_lots
(1.12M) делал Bitmap Heap Scan ~41k блоков (cold 5.3s/warm 474ms), пробивая p95 ≤3с.
Partial covering-index (district, price_per_m2_rub) WHERE price_per_m2_rub IS NOT NULL →
Index Only Scan (verified scratch: 87ms, Heap Fetches 0). Требует VACUUM ANALYZE
objective_lots post-deploy (stale VM иначе глушит выигрыш). database-expert  verified.

#79: 152-ФЗ PII grep-аудит — plain-PII в логах НЕ найдено; Sentry scrub / PRINZIP-хеширование
/ LLM §19-redaction подтверждены. Остаточный риск: pilot_requests хранит сырые ПДн без
retention (рекомендация в docs). httpx DEBUG off (ops-инвариант).

Closes #70
Closes #79
2026-06-14 00:09:09 +05:00

45 lines
3.3 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.

-- 153_objective_lots_district_price_idx.sql
-- Issue #70 (B4-3 Performance аудит) — covering-index для analyze district-price block.
--
-- ПРОБЛЕМА (EXPLAIN ANALYZE на prod, district='Академический', 2026-06-13):
-- app/api/v1/parcels.py:2123 (analyze hot-path, "B5-5 District price ranges")
-- SELECT MIN/MAX/PERCENTILE_CONT(price_per_m2_rub), COUNT(*)
-- FROM objective_lots
-- WHERE district = :dn AND price_per_m2_rub IS NOT NULL
-- AND price_per_m2_rub BETWEEN 30000 AND 600000
-- На плотном районе матчит ~182k строк (objective_lots ~1.12M строк всего).
-- Существующий objective_lots_district_class_idx (district, class) даёт только
-- district-match по индексу, НО price_per_m2_rub лежит в heap → Bitmap Heap Scan
-- читает ~41k heap-блоков (~320 МБ) чтобы достать цену каждой строки.
-- Замеры: cold-cache 5286 ms, warm-cache 499 ms — оба пробивают analyze
-- p95-таргет ≤3 с (issue #70 targets) на холодном кэше / плотных районах.
--
-- РЕШЕНИЕ: частичный covering-index (district, price_per_m2_rub). Обе колонки —
-- и фильтр, и агрегируемое значение — в индексе → планировщик может сделать
-- Index Only Scan (читает ~283 листовых страницы индекса вместо 41k heap-блоков),
-- heap не трогается (видимость закрывает visibility map после VACUUM).
-- WHERE price_per_m2_rub IS NOT NULL — частичный: индексируем только строки с
-- ценой (~919k из 1.12M), отсекая ~204k NULL-цен; зеркалит предикат запроса
-- (IS NOT NULL) и сужает индекс. Конкретный район в запросе делает Index Cond
-- по первой колонке, BETWEEN — диапазон по второй.
--
-- Dependencies: objective_lots (существует; обновляется objective_etl). PG 16.4.
-- Idempotent: CREATE INDEX IF NOT EXISTS. Не-CONCURRENTLY намеренно — deploy.yml
-- гоняет файл в транзакции (BEGIN/COMMIT), а CREATE INDEX CONCURRENTLY в tx
-- запрещён. Index ~45 MB, build несколько секунд; обычный CREATE INDEX берёт
-- SHARE-lock (блокирует только writes/objective_etl, НЕ analyze-читателей) —
-- приемлемо в deploy-окне (как индексы 87/97). Не запускать одновременно с ETL.
-- Applied: automatically via deploy.yml in NN order (трекер _schema_migrations).
BEGIN;
CREATE INDEX IF NOT EXISTS idx_objective_lots_district_price
ON objective_lots (district, price_per_m2_rub)
WHERE price_per_m2_rub IS NOT NULL;
COMMENT ON INDEX idx_objective_lots_district_price IS
'Covering-index для analyze district-price block (#70): district-фильтр + '
'агрегаты по price_per_m2_rub через Index Only Scan, без Bitmap Heap Scan '
'~41k блоков. См. parcels.py:2123, docs/Performance_Audit.md.';
COMMIT;