perf(analyze): partial index on objective_corpus_room_month (~3.8x on hot velocity baseline) #1942

Merged
bot-backend merged 1 commit from perf/171-objective-crm-report-month-idx into main 2026-06-27 05:02:42 +00:00
Collaborator

Что

Partial-индекс на objective_corpus_room_month (report_month) WHERE deals_total_count > 0. Ускоряет EKB-wide velocity-baseline, который считается на КАЖДОМ /analyze.

Почему (найдено через pg_stat_user_tables seq-scan аудит + database-expert EXPLAIN)

_get_ekb_median() (velocity.py:706) → compute_velocity()POST /parcels/{cad}/analyze (parcels.py:3157) — раз на каждый analyze (самый горячий эндпоинт). Предикаты report_month >= now-6mo AND deals_total_count > 0 не имели пригодного индекса (5 существующих report_month-индексов не partial по deals_total_count; голый range = 27% строк → планнер корректно выбирал Seq Scan по всей таблице ~95 MB).

Доказательство (prod EXPLAIN ANALYZE, BEGIN/ROLLBACK)

До После
План Seq Scan (74589 rows removed) Bitmap/Index Scan
Время 144 ms 38 ms (~3.8x)
Buffers 12281 3136 (−74%)

Планнер использует индекс естественно (без enable_seqscan хаков). Независимо перепроверил dry-run'ом: Index Only Scan using idx_objective_crm_report_month_sold, 2747 buffers, 2.9ms на фильтре.

Безопасность

  • Индекс ~280 kB (8.9% селективность). Запись в таблицу — ТОЛЬКО еженедельный ETL (objective_etl.py:288), не request-path → write-overhead пренебрежим.
  • IF NOT EXISTS (идемпотентно). Плейн CREATE INDEX (не CONCURRENTLY — нельзя в BEGIN/COMMIT миграции); build sub-second, SHARE-lock блокирует лишь weekly-ETL writer, не analyze-читателей.
  • Никаких code-изменений — прозрачно для запроса.

Migration data/sql/171_*. Database-expert проверил и 3 других seq-scan-таблицы (objective_lots/cad_buildings уже оптимизированы #70; domrf_kn_flats нужен query-rewrite, не индекс — отдельный follow-up).

## Что Partial-индекс на `objective_corpus_room_month (report_month) WHERE deals_total_count > 0`. Ускоряет EKB-wide velocity-baseline, который считается **на КАЖДОМ `/analyze`**. ## Почему (найдено через `pg_stat_user_tables` seq-scan аудит + database-expert EXPLAIN) `_get_ekb_median()` (`velocity.py:706`) → `compute_velocity()` → `POST /parcels/{cad}/analyze` (`parcels.py:3157`) — раз на каждый analyze (самый горячий эндпоинт). Предикаты `report_month >= now-6mo AND deals_total_count > 0` не имели пригодного индекса (5 существующих report_month-индексов не partial по `deals_total_count`; голый range = 27% строк → планнер корректно выбирал Seq Scan по всей таблице ~95 MB). ## Доказательство (prod EXPLAIN ANALYZE, BEGIN/ROLLBACK) | | До | После | |---|---|---| | План | Seq Scan (74589 rows removed) | Bitmap/Index Scan | | Время | **144 ms** | **38 ms** (~3.8x) | | Buffers | 12281 | 3136 (−74%) | Планнер использует индекс **естественно** (без `enable_seqscan` хаков). Независимо перепроверил dry-run'ом: `Index Only Scan using idx_objective_crm_report_month_sold`, 2747 buffers, 2.9ms на фильтре. ## Безопасность - Индекс **~280 kB** (8.9% селективность). Запись в таблицу — ТОЛЬКО еженедельный ETL (`objective_etl.py:288`), не request-path → write-overhead пренебрежим. - `IF NOT EXISTS` (идемпотентно). Плейн `CREATE INDEX` (не CONCURRENTLY — нельзя в BEGIN/COMMIT миграции); build sub-second, SHARE-lock блокирует лишь weekly-ETL writer, не analyze-читателей. - Никаких code-изменений — прозрачно для запроса. Migration `data/sql/171_*`. Database-expert проверил и 3 других seq-scan-таблицы (objective_lots/cad_buildings уже оптимизированы #70; domrf_kn_flats нужен query-rewrite, не индекс — отдельный follow-up).
bot-backend added 1 commit 2026-06-27 04:50:02 +00:00
perf(analyze): partial index on objective_corpus_room_month for EKB velocity baseline
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m49s
CI / backend-tests (pull_request) Successful in 12m4s
116af2ebc1
_get_ekb_median() (velocity.py:706) runs on EVERY POST /parcels/{cad}/analyze
(the hottest endpoint) and seq-scanned the whole objective_corpus_room_month
(~95MB, ~12159 buffers, 144ms) — its predicates (report_month >= now-6mo AND
deals_total_count > 0) had no usable index (the 5 existing report_month indexes
aren't partial on deals_total_count; a bare range matches 27% of rows, so the
planner correctly chose Seq Scan).

Add partial b-tree (report_month) WHERE deals_total_count > 0 (~280kB, 8.9%
selectivity). Prod EXPLAIN (BEGIN/ROLLBACK): 144ms→38ms (~3.8x), buffers
12281→3136 (-74%); planner uses it naturally (Index/Bitmap scan). Independently
dry-run-verified: Index Only Scan, 2747 buffers.

Write cost negligible (objective_corpus_room_month written only by weekly ETL,
not request-path). Idempotent (IF NOT EXISTS); plain CREATE INDEX (not
CONCURRENTLY, can't run in the migration's BEGIN/COMMIT) — sub-second build,
SHARE lock blocks only the weekly ETL writer, not analyze readers.

Found via pg_stat_user_tables seq-scan audit + database-expert EXPLAIN analysis.
bot-backend merged commit 47fe8e2469 into main 2026-06-27 05:02:42 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1942
No description provided.