diff --git a/tradein-mvp/backend/data/sql/104_index_hygiene_geom_dedup.sql b/tradein-mvp/backend/data/sql/104_index_hygiene_geom_dedup.sql new file mode 100644 index 00000000..17368da3 --- /dev/null +++ b/tradein-mvp/backend/data/sql/104_index_hygiene_geom_dedup.sql @@ -0,0 +1,52 @@ +-- 104_index_hygiene_geom_dedup.sql +-- #730 index hygiene (follow-up to 097_index_hygiene.sql). +-- +-- Context: +-- Issue #730 listed 5 duplicate/redundant indexes + 5 is_active=true partials to +-- de-partial. ALL of those were already handled by 097_index_hygiene.sql, which is +-- applied on prod (verified in _schema_migrations, 2026-06-13). Re-verification on +-- prod confirms every audit target is already gone / already de-partialed: +-- houses_kadastr_idx2, houses_addr_fp_idx, listings_scraped_idx, +-- listing_sources_price_divergence_idx, houses_source_ext_idx -> ABSENT (dropped by 097) +-- listings_geom_active_idx, listings_active_filter_idx, listings_house_price_idx, +-- listings_scraped_desc_idx, listings_rooms_area_idx -> already full (de-partialed by 097) +-- No is_active partial index remains anywhere in the public schema. +-- +-- This migration handles the ONE remaining duplicate that the original audit could +-- not see, because it only emerged AS A SIDE-EFFECT of 097's de-partial: +-- +-- listings_geom_active_idx (050_search_optimization.sql:47) was originally +-- GIST(geom) WHERE is_active = true. 097 stripped the predicate, so it is now +-- GIST(geom) with no predicate — an EXACT duplicate of listings_geom_idx +-- (002_core_tables.sql:57), the original full GIST on the same column. +-- +-- Prod evidence (2026-06-13, tradein-postgres): +-- listings_geom_idx CREATE INDEX ... USING gist (geom) scan=131 7944 kB (KEEP, original) +-- listings_geom_active_idx CREATE INDEX ... USING gist (geom) scan=83 3240 kB (DROP, duplicate) +-- Both back no constraint (standalone). Dropping the duplicate frees ~3.2 MB and +-- removes redundant write/VACUUM overhead. listings_geom_idx fully covers every +-- spatial predicate the dropped index served (incl. the active-only queries, which +-- simply add a WHERE is_active filter on top of the same GIST scan). +-- +-- NOTE on the original "is_active=100%" premise: that premise is now STALE. As of +-- 2026-06-13 prod shows 19312 active / 39115 total listings (~49% active), so a +-- WHERE is_active=true partial would NOT be pointless today. We are NOT re-adding +-- the predicate here — listings_geom_idx (full) already covers the workload and a +-- second geom index (partial or full) is pure redundancy. Flagged for awareness. +-- +-- Dependencies: 002_core_tables.sql (listings_geom_idx), 050_search_optimization.sql +-- + 097_index_hygiene.sql (which de-partialed listings_geom_active_idx). +-- Deploy order: any time after 097. Schema-only, low risk (tradein DB), short lock. +-- DROP INDEX (non-CONCURRENTLY) is fine inside this transaction: brief +-- ACCESS EXCLUSIVE on listings only, no CONCURRENTLY (which is illegal +-- inside a txn block). +-- +-- Idempotency: DROP INDEX IF EXISTS. Safe to re-run. + +BEGIN; + +-- Exact duplicate of listings_geom_idx (002_core_tables.sql:57) after 097 removed its +-- WHERE is_active=true predicate. Keep the original; drop this redundant twin. +DROP INDEX IF EXISTS listings_geom_active_idx; + +COMMIT;