Прод 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 строк). |
||
|---|---|---|
| .. | ||
| analysis_runs | ||
| analytics | ||
| cadastre | ||
| chat | ||
| etl | ||
| exporters | ||
| forecasting | ||
| generative | ||
| llm | ||
| photos | ||
| scrapers | ||
| site_finder | ||
| __init__.py | ||
| analytics_queries.py | ||
| analytics_refresh.py | ||
| forecast_request_cache.py | ||
| insights.py | ||
| job_settings.py | ||
| objective_etl.py | ||
| objective_sync_config.py | ||
| own_projects.py | ||
| weather_cache.py | ||