Прод EXPLAIN-охота (follow-up эпика #1953): #1 competitors._SOLD_COUNT_SQL — фикс регрессии #1964. JOIN v_objective_lots_latest материализовал DISTINCT ON по всей 1.76M-таблице ДО фильтра маппинга (qual не проталкивается ниже view-DISTINCT ON) → Parallel Seq Scan + external sort (~213 MB temp). Переписан на inline-pushdown (зеркало #1964 / market_metrics._STOCK_SQL): фильтр objective_complex_mapping в CTE → DISTINCT ON по СЫРОЙ objective_lots, JOIN ON project_name (objective_lots_project_idx). Прод: 7958 → 292 ms, вывод побайтово идентичен (15/15 obj_id, 0 mismatch). COUNT(DISTINCT objective_lot_id) сохранён (fan-out-safety). #2/#6/#5 миграция 176 — покрывающий physflat-индекс. EKB-wide _STOCK_SQL/_SALES_WINDOW_SQL full-seq-scan + external sort (~140 MB temp), т.к. partial-индекс 173 не нёс проецируемых колонок. Новый objective_lots_physflat_covering_idx = тот же ключ + INCLUDE(rooms_int, area_pd, sales_start_date, is_sold, contract_date, status, class) + WHERE premise_kind='квартира'; старый _latest_idx дропнут (полностью замещён). Прод DRY-RUN: Index Only Scan + Unique, без external sort, 5177 → 1595 ms; индекс 252 MB. ANALYZE objective_lots в конце tx. Деплой вне недельного окна objective-scrape (кратковременный SHARE-lock при drop+create ~252 MB индекса на 1.75M строк). |
||
|---|---|---|
| .. | ||
| api | ||
| core | ||
| models | ||
| observability | ||
| schemas | ||
| scrapers | ||
| services | ||
| templates | ||
| workers | ||
| __init__.py | ||
| main.py | ||