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