(low) De-partиализация 5 listings-индексов в 097 на ложной посылке «is_active = 100% true», которую 104 подтверждает устаревшей #1398

Closed
opened 2026-06-14 18:11:27 +00:00 by bot-backend · 0 comments
Collaborator

Описание

Миграция 097_index_hygiene.sql (Step 2) убрала предикат WHERE is_active = true из 5 индексов на таблице listings (listings_geom_active_idx, listings_active_filter_idx, listings_house_price_idx, listings_scraped_desc_idx, listings_rooms_area_idx), превратив их в полные (non-partial) индексы. Обоснование — «is_active is 100% true in production». Более поздняя миграция 104_index_hygiene_geom_dedup.sql (тот же issue #730, применена после 097) прямо фиксирует, что эта посылка УСТАРЕЛА: на проде на 2026-06-13 — 19312 active / 39115 total (~49% активных). Hot-path эстиматора/поиска фильтрует только активные листинги, поэтому полные индексы теперь индексируют ~51% «мёртвых» строк: индекс крупнее, а планировщик вынужден делать дополнительный heap-recheck для active-only аналог-запросов. 104 удаляет только дубликат listings_geom_active_idx (после de-partial он стал точным дублем listings_geom_idx), но НЕ возвращает предикаты на composite-фильтр-индексы — регрессия остаётся в проде.

Корень

Индексы строились под допущение, что is_active всегда true, что делало partial-предикат бесполезным. Допущение перестало выполняться (накопились неактивные листинги, ~49% активных), но индексы остались полными. Composite-индексы по столбцам, которыми оперируют active-only запросы (rooms, area_m2, price_rub, house_id_fk), теперь покрывают вдвое больше строк, чем нужно hot-path.

Evidence

097_index_hygiene.sql:57-58 — обоснование de-partial на ложной посылке:

-- Step 2: De-partial is_active=true indexes
-- Since is_active is 100% true, the WHERE predicate saves nothing.

097_index_hygiene.sql:70-72 — пример composite-фильтр-индекса, потерявшего предикат:

DROP INDEX IF EXISTS listings_active_filter_idx;
CREATE INDEX IF NOT EXISTS listings_active_filter_idx
    ON listings (rooms, price_rub, area_m2, scraped_at DESC);

104_index_hygiene_geom_dedup.sql:31-35 — прямое опровержение посылки, без восстановления предикатов на composite-индексы:

--   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 ... Flagged for awareness.

Hot-path, использующий active-only фильтр поверх де-партиализованных индексов:

estimator.py:1469   WHERE is_active = true
estimator.py:1514   WHERE is_active = true
estimator.py:3226   AND is_active = true
estimator.py:3579   AND is_active = true

Эти запросы фильтруют по rooms/area_m2/price_rub/house_id_fk, совпадающим с составом де-партиализованных composite-индексов; те же столбцы использует app/tasks/asking_to_sold_ratio.py.

Suggested fix

В follow-up миграции вернуть WHERE is_active = true на composite-фильтр-индексы, которые обслуживают active-only аналог-запросы эстиматора: listings_active_filter_idx, listings_house_price_idx, listings_rooms_area_idx. Geom-индекс оставить единственным полным согласно 104 (listings_geom_idx), partial-дубликат geom не воссоздавать. Использовать CREATE INDEX CONCURRENTLY вне транзакции, чтобы не брать ACCESS EXCLUSIVE на проде.

Обоснование severity: low

Баг реальный и подтверждён кодом (посылка «100% true» доказуемо ложна, регрессия в проде), но текущее влияние минорное: (a) таблица всего ~39k строк — абсолютный оверхед на размер индекса и heap-filter мал; (b) корректность не нарушена — индексы продолжают обслуживать запросы через full index scan + heap filter, есть только субоптимальный размер и небольшая дополнительная фильтрация; (c) 104 уже задокументировала устаревшую посылку как known trade-off, это не недосмотр; (d) влияние станет ощутимым лишь по мере дальнейшего накопления неактивных строк. Отсюда low, а не medium.


week ревью 1 · multi-agent аудит 2026-06-14 · severity=low · category=performance

Затронутые файлы:

  • /Users/anton/Птица/gendesign/tradein-mvp/backend/data/sql/097_index_hygiene.sql
  • /Users/anton/Птица/gendesign/tradein-mvp/backend/data/sql/104_index_hygiene_geom_dedup.sql
  • /Users/anton/Птица/gendesign/tradein-mvp/backend/app/services/estimator.py
## Описание Миграция `097_index_hygiene.sql` (Step 2) убрала предикат `WHERE is_active = true` из 5 индексов на таблице `listings` (`listings_geom_active_idx`, `listings_active_filter_idx`, `listings_house_price_idx`, `listings_scraped_desc_idx`, `listings_rooms_area_idx`), превратив их в полные (non-partial) индексы. Обоснование — «is_active is 100% true in production». Более поздняя миграция `104_index_hygiene_geom_dedup.sql` (тот же issue #730, применена после 097) прямо фиксирует, что эта посылка УСТАРЕЛА: на проде на 2026-06-13 — 19312 active / 39115 total (~49% активных). Hot-path эстиматора/поиска фильтрует только активные листинги, поэтому полные индексы теперь индексируют ~51% «мёртвых» строк: индекс крупнее, а планировщик вынужден делать дополнительный heap-recheck для active-only аналог-запросов. 104 удаляет только дубликат `listings_geom_active_idx` (после de-partial он стал точным дублем `listings_geom_idx`), но НЕ возвращает предикаты на composite-фильтр-индексы — регрессия остаётся в проде. ## Корень Индексы строились под допущение, что `is_active` всегда true, что делало partial-предикат бесполезным. Допущение перестало выполняться (накопились неактивные листинги, ~49% активных), но индексы остались полными. Composite-индексы по столбцам, которыми оперируют active-only запросы (`rooms`, `area_m2`, `price_rub`, `house_id_fk`), теперь покрывают вдвое больше строк, чем нужно hot-path. ## Evidence `097_index_hygiene.sql:57-58` — обоснование de-partial на ложной посылке: ```sql -- Step 2: De-partial is_active=true indexes -- Since is_active is 100% true, the WHERE predicate saves nothing. ``` `097_index_hygiene.sql:70-72` — пример composite-фильтр-индекса, потерявшего предикат: ```sql DROP INDEX IF EXISTS listings_active_filter_idx; CREATE INDEX IF NOT EXISTS listings_active_filter_idx ON listings (rooms, price_rub, area_m2, scraped_at DESC); ``` `104_index_hygiene_geom_dedup.sql:31-35` — прямое опровержение посылки, без восстановления предикатов на composite-индексы: ```sql -- 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 ... Flagged for awareness. ``` Hot-path, использующий active-only фильтр поверх де-партиализованных индексов: ``` estimator.py:1469 WHERE is_active = true estimator.py:1514 WHERE is_active = true estimator.py:3226 AND is_active = true estimator.py:3579 AND is_active = true ``` Эти запросы фильтруют по `rooms`/`area_m2`/`price_rub`/`house_id_fk`, совпадающим с составом де-партиализованных composite-индексов; те же столбцы использует `app/tasks/asking_to_sold_ratio.py`. ## Suggested fix В follow-up миграции вернуть `WHERE is_active = true` на composite-фильтр-индексы, которые обслуживают active-only аналог-запросы эстиматора: `listings_active_filter_idx`, `listings_house_price_idx`, `listings_rooms_area_idx`. Geom-индекс оставить единственным полным согласно 104 (`listings_geom_idx`), partial-дубликат geom не воссоздавать. Использовать `CREATE INDEX CONCURRENTLY` вне транзакции, чтобы не брать ACCESS EXCLUSIVE на проде. ## Обоснование severity: low Баг реальный и подтверждён кодом (посылка «100% true» доказуемо ложна, регрессия в проде), но текущее влияние минорное: (a) таблица всего ~39k строк — абсолютный оверхед на размер индекса и heap-filter мал; (b) корректность не нарушена — индексы продолжают обслуживать запросы через full index scan + heap filter, есть только субоптимальный размер и небольшая дополнительная фильтрация; (c) 104 уже задокументировала устаревшую посылку как known trade-off, это не недосмотр; (d) влияние станет ощутимым лишь по мере дальнейшего накопления неактивных строк. Отсюда low, а не medium. --- _week ревью 1 · multi-agent аудит 2026-06-14 · severity=**low** · category=`performance`_ Затронутые файлы: - `/Users/anton/Птица/gendesign/tradein-mvp/backend/data/sql/097_index_hygiene.sql` - `/Users/anton/Птица/gendesign/tradein-mvp/backend/data/sql/104_index_hygiene_geom_dedup.sql` - `/Users/anton/Птица/gendesign/tradein-mvp/backend/app/services/estimator.py`
bot-backend added the
week ревью 1
label 2026-06-14 18:11:27 +00:00
bot-backend added the
scope/backend
label 2026-06-15 11:01:58 +00:00
bot-backend added the
needs-human
label 2026-06-15 16:44:03 +00:00
Sign in to join this conversation.
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#1398
No description provided.