gendesign/data/sql/177_drop_dead_objective_lots_indexes.sql
bot-backend c97dd57e1c
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 33s
Deploy / build-worker (push) Successful in 34s
Deploy / deploy (push) Successful in 8m34s
perf(objective_lots): drop 3 dead indexes (status/rooms_area/bank) — write-amp (#1953) (#2056)
2026-06-28 14:39:05 +00:00

65 lines
5 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.

-- Контекст: perf-hunt finding #4 (follow-up эпика #1953) — срезаем
-- write-amplification на горячей UPSERT-таблице objective_lots.
--
-- objective_lots — append/UPSERT-per-snapshot, очень write-heavy: за 35 дней
-- аптайма (pg_stat без reset, prod 2026-06-28) n_tup_ins=1.76M, n_tup_upd=1.39M.
-- Каждый INSERT/UPDATE обязан поддерживать ВСЕ индексы таблицы → мёртвый индекс =
-- чистый write-amp + место на диске + WAL, ноль пользы для чтения.
--
-- Дропаем 3 индекса, у которых pg_stat_user_indexes.idx_scan почти нулевой за весь
-- аптайм И в кодовой базе (backend services/api/workers + data/sql views) НЕТ ни
-- одного sargable-запроса, который мог бы их использовать. По каждому проверено
-- EXPLAIN на prod: планировщик берёт Seq Scan на реальной форме запроса даже при
-- наличии индекса (доказывает, что индекс не служит ничему).
--
-- ── DROP 1: objective_lots_status_idx ── (idx_scan=8 за 35д, 19 MB)
-- Опр.: ON objective_lots(status) WHERE status IS NOT NULL (68_schema_objective.sql:250)
-- Единственные потребители status: market_metrics._STOCK_SQL,
-- supply_layers, competitors, special_indices — ВСЕ через
-- `LOWER(COALESCE(ol.status,'')) = 'продан'`. Обёртка LOWER(COALESCE(...))
-- НЕ sargable против plain B-tree на status → индекс не используется НИКОГДА.
-- Bare `status = :x` / `status IN (...)` в коде НЕ встречается (grep пуст).
-- Колонка status уже в payload covering-индекса (176_*, INCLUDE) — для проекции
-- отдельный индекс тоже не нужен. Триггер истории (166_*) читает NEW.status /
-- prev.status построчно (без индекса).
-- EXPLAIN prod: `WHERE LOWER(COALESCE(status,''))='продан'` → Parallel Seq Scan.
--
-- ── DROP 2: objective_lots_rooms_area_idx ── (idx_scan=7 за 35д, 78 MB)
-- Опр.: ON objective_lots(rooms_int, area_pd) WHERE area_pd IS NOT NULL (:252)
-- rooms_int / area_pd встречаются только в SELECT-проекциях и внутри CASE-bucket
-- выражений (market_metrics, sales_series, analytics) — НЕ как WHERE/ORDER/JOIN
-- предикат. Room-bucket фильтр оборачивает обе колонки в CASE → не sargable.
-- Никакого `WHERE rooms_int = / IN` или `area_pd >/</BETWEEN`-фильтра в коде нет
-- (grep пуст). Драйвер этих запросов — district/premise_kind/class
-- (objective_lots_district_class_idx, idx_scan=49746 — жив, НЕ трогаем).
-- EXPLAIN prod: CASE-bucket-форма → Parallel Seq Scan; индекс не выбран.
--
-- ── DROP 3: objective_lots_bank_idx ── (idx_scan=2 за 35д, 8 KB)
-- Опр.: ON objective_lots(bank_name) WHERE bank_name IS NOT NULL (:256)
-- Единственный потребитель objective_lots.bank_name — special_indices
-- ._ARTIFICIAL_DEMAND_SQL: `(ol.encumbrance_type IS NOT NULL OR
-- ol.bank_name IS NOT NULL) AS has_mortgage` — это проекция в SELECT, не фильтр;
-- WHERE там по premise_kind/is_sold/contract_date/district/class. Запросы с
-- `WHERE bank_name = ...` есть только к ДРУГОЙ таблице (domrf_rates,
-- 40_relations.sql:270/273), не к objective_lots.
-- EXPLAIN prod: реальная форма → Parallel Seq Scan; индекс не выбран.
--
-- НЕ ТРОГАЕМ: objective_lots_project_name_trgm_idx (236 MB, idx_scan=19) — у него
-- ETL-потребитель (objective_backfill.py, similarity()); дроп требует отдельной
-- проверки ETL-каденса → отдельный тикет, вне scope этой миграции.
--
-- Откат: пересоздать из data/sql/68_schema_objective.sql:250-256
-- (CREATE INDEX IF NOT EXISTS ...). Дроп индекса обратим, данные не теряются.
--
-- Порядок применения для prod: миграция самодостаточна (ничего в app-коде не
-- зависит от этих индексов — потому и дропаем), порядок deploy не критичен.
-- Idempotent (DROP INDEX IF EXISTS). Self-wrapped BEGIN/COMMIT.
-- Экономия: ~97 MB диска + соответствующий WAL/maintenance write-amp на каждый
-- из ~3.15M lifetime INSERT/UPDATE.
BEGIN;
DROP INDEX IF EXISTS objective_lots_status_idx;
DROP INDEX IF EXISTS objective_lots_rooms_area_idx;
DROP INDEX IF EXISTS objective_lots_bank_idx;
COMMIT;