gendesign/data/sql/171_objective_crm_report_month_idx.sql
Light1YT 23cc188972
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m52s
CI / backend-tests (pull_request) Successful in 12m0s
fix(perf): move migration 171 to repo-root data/sql/ so the deploy applies it
The objective_corpus_room_month index migration (#1942) was committed to
backend/data/sql/171_*, but the main deploy runner applies migrations from
repo-root data/sql/*.sql (deploy.yml:280) and the path trigger is data/sql/**.
So the file never ran — prod still seq-scans (verified post-deploy: index
absent, query 265ms). Move it to data/sql/171_* (alongside 170) so it deploys.
No SQL change; the index itself was dry-run-verified on prod (BEGIN/ROLLBACK).
2026-06-27 10:06:33 +05:00

45 lines
2.9 KiB
PL/PgSQL
Raw Permalink 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.

-- 171_objective_crm_report_month_idx.sql
-- Partial index ускоряющий EKB-wide velocity-baseline на КАЖДОМ /analyze.
--
-- WHY (hot-path perf, найдено через pg_stat_user_tables + EXPLAIN ANALYZE на проде):
-- `_get_ekb_median()` (app/services/site_finder/velocity.py:706) вызывается
-- безусловно из compute_velocity() → внутри POST /parcels/{cad}/analyze
-- (parcels.py:3157) — т.е. РАЗ НА КАЖДЫЙ analyze (самый горячий эндпоинт).
-- Он агрегирует EKB-wide медиану sqm/мес по всем проектам, без district/project
-- фильтра; единственные предикаты — `report_month >= CURRENT_DATE - 6mo`
-- AND `deals_total_count > 0`.
--
-- До индекса: Seq Scan по всей objective_corpus_room_month (81893 строк, ~95 MB,
-- ~12159 блоков) на каждый analyze. EXPLAIN (ANALYZE,BUFFERS) на проде:
-- Seq Scan ... Rows Removed by Filter: 74589 (keeps 7304); Execution Time: 144 ms.
-- Существующие 5 индексов с report_month НЕ помогают: ни один не partial по
-- deals_total_count, а голый `report_month >=` диапазон = 27% строк (не
-- селективно). С enable_seqscan=off планнер падает на full index scan (6100
-- буферов / 68 ms) — хуже seq scan, поэтому по умолчанию выбирает Seq Scan.
--
-- WHAT:
-- Partial b-tree на (report_month) WHERE deals_total_count > 0. Реальная
-- селективность запроса 8.9% (7304/81893) → bitmap index scan.
-- После (проверено BEGIN…ROLLBACK на проде):
-- Bitmap Heap Scan ... rows=7304; Execution Time: 37.6 ms; buffers ~3136.
-- 144 ms → 38 ms (~3.8x), buffers 12281 → 3136 (74%). Планнер использует
-- индекс ЕСТЕСТВЕННО (без enable_seqscan-хаков).
--
-- COST: индекс ~280 kB (37204 строк matching partial-предикат). Запись в таблицу
-- идёт ТОЛЬКО еженедельным ETL (objective_etl.py:288), не на request-path →
-- write-overhead пренебрежим. Build sub-second (плейн CREATE INDEX, SHARE-lock
-- блокирует лишь weekly-ETL writer, не analyze-читателей).
--
-- IDEMPOTENCY: CREATE INDEX IF NOT EXISTS → повторный прогон no-op.
-- NB: плейн CREATE INDEX (НЕ CONCURRENTLY) — CONCURRENTLY нельзя внутри BEGIN/COMMIT,
-- а deploy-runner оборачивает миграцию в транзакцию; для 280 kB это безопасно.
--
-- Deploy order: после 170_osm_utility_infrastructure_ekb.sql.
BEGIN;
CREATE INDEX IF NOT EXISTS idx_objective_crm_report_month_sold
ON objective_corpus_room_month (report_month)
WHERE deals_total_count > 0;
COMMIT;