[SF][perf] Ускорить §22 forecast — распараллелить 4 горизонта (~4мин → ~1мин) #1129

Closed
opened 2026-06-07 11:36:58 +00:00 by lekss361 · 2 comments
Owner

Проблема

Холодный расчёт прогноза §22 (forecast_site_finder_report) измерен на проде 2026-06-07 (Celery-логи, 3 замера): 215–233 сек (~3.5–4 мин) на участок. Кэш — мгновенно (~1s). UI показывает «Прогноз рассчитывается…» все ~4 минуты → плохой UX на демо.

Гипотеза узкого места

Один таск считает все 4 горизонта последовательно (лог: horizons=[6, 12, 18, 24] в одном прогоне). Если расчёт по-горизонтный независим → распараллеливание даёт ~4× (≈1 мин).

Что сделать

  1. Профилировать forecast_site_finder_report / движок §9.x — где время (по-горизонтный цикл vs общие данные).
  2. Если горизонты независимы — считать их параллельно (ThreadPool/несколько Celery-сабтасков с chord, либо векторизовать общую часть один раз + разветвить только горизонт-специфику).
  3. Цель: cold ≤ ~90с. Замер до/после теми же 3 cad.

Альтернативы/дополнительно

  • Precompute-прогрев кэша прогнозов по ключевым демо-участкам заранее (cron) — мгновенный показ на демо.
  • Прогресс-индикатор в UI (% / этап) вместо статичного «рассчитывается».

scope/backend, site-finder, GG-форсайт. Измерено в сессии 2026-06-07.

## Проблема Холодный расчёт прогноза §22 (`forecast_site_finder_report`) измерен на проде 2026-06-07 (Celery-логи, 3 замера): **215–233 сек (~3.5–4 мин)** на участок. Кэш — мгновенно (~1s). UI показывает «Прогноз рассчитывается…» все ~4 минуты → плохой UX на демо. ## Гипотеза узкого места Один таск считает все 4 горизонта последовательно (лог: `horizons=[6, 12, 18, 24]` в одном прогоне). Если расчёт по-горизонтный независим → распараллеливание даёт ~4× (≈1 мин). ## Что сделать 1. Профилировать `forecast_site_finder_report` / движок §9.x — где время (по-горизонтный цикл vs общие данные). 2. Если горизонты независимы — считать их параллельно (ThreadPool/несколько Celery-сабтасков с chord, либо векторизовать общую часть один раз + разветвить только горизонт-специфику). 3. Цель: cold ≤ ~90с. Замер до/после теми же 3 cad. ## Альтернативы/дополнительно - Precompute-прогрев кэша прогнозов по ключевым демо-участкам заранее (cron) — мгновенный показ на демо. - Прогресс-индикатор в UI (% / этап) вместо статичного «рассчитывается». scope/backend, site-finder, GG-форсайт. Измерено в сессии 2026-06-07.
lekss361 added the
GG-форсайт
performance
scope/backend
site-finder
labels 2026-06-07 11:37:52 +00:00
Collaborator

Замерено на проде — 215-233с → 12.6с (~17×)

PR #1160 (8206a0b) задеплоен. Замер cold build_site_finder_report на проде (gendesign-worker-1, cad 66:41:0702048:27, district Кировский, сетка 6/12/18/24, read-only — без persist):

COLD build_site_finder_report -> 12.6s  (baseline 215-233s);  horizons rendered=4

~17× ускорение, далеко за целью ≤90с. «Прогноз рассчитывается…» 4 минуты → ~13 секунд.

Корень (профиль): §9.x пере-грузили горизонт/сегмент-инвариантные БД-загрузки сотни раз на отчёт (get_monthly_macro ×290, market_metrics ×124, get_competitors ×69). Per-request memoization-кэш (ContextVar + @cached) → каждая уникальная загрузка ОДИН раз. Без конкурентности, вывод побайтово идентичен (frozen-продьюсеры, read-only callers, per-report scope), детерминизм §16/§26 сохранён. 58 тестов (key-discrimination regression-guard + amplification proof ×N→1), code-reviewer .

Прекомпьют-прогрев кэша по демо-участкам (альтернатива из тикета) больше не нужен — 13с достаточно для живого демо.

### ✅ Замерено на проде — 215-233с → **12.6с** (~17×) PR #1160 (`8206a0b`) задеплоен. Замер cold `build_site_finder_report` на проде (`gendesign-worker-1`, cad `66:41:0702048:27`, district Кировский, сетка 6/12/18/24, read-only — без persist): ``` COLD build_site_finder_report -> 12.6s (baseline 215-233s); horizons rendered=4 ``` **~17× ускорение, далеко за целью ≤90с.** «Прогноз рассчитывается…» 4 минуты → ~13 секунд. Корень (профиль): §9.x пере-грузили горизонт/сегмент-инвариантные БД-загрузки сотни раз на отчёт (`get_monthly_macro ×290`, `market_metrics ×124`, `get_competitors ×69`). Per-request memoization-кэш (ContextVar + `@cached`) → каждая уникальная загрузка ОДИН раз. Без конкурентности, вывод побайтово идентичен (frozen-продьюсеры, read-only callers, per-report scope), детерминизм §16/§26 сохранён. 58 тестов (key-discrimination regression-guard + amplification proof ×N→1), code-reviewer ✅. Прекомпьют-прогрев кэша по демо-участкам (альтернатива из тикета) больше не нужен — 13с достаточно для живого демо.
Collaborator

Замер 2026-06-12 — цель достигнута через forecast_cache (PR #1160)

forecast_request_cache (per-request memoization, мерж PR #1160 2026-06-08) сорвал амплификацию одних и тех же горизонт-инвариантных БД-загрузок: get_monthly_macro ×290 → ×1, compute_market_metrics ×124 → ×1, get_competitors ×69 → 1-на-горизонт, аналогично §9.4/§9.5/§9.6. Ничего не параллелим — выгрызаем дубликаты на одном потоке.

Honest cold-cache замер на проде

docker exec gendesign-backend-1 pythonbuild_site_finder_report(...) напрямую (мимо Celery, без переиспользования таблицы analysis_runs):

параметр значение
cad_num 66:41:0304011:1124 (самый тяжёлый в анамнезе: 100 ранов)
district None (ЕКБ-wide — широчайший pipeline)
horizons 4 (6/12/18/24)
scenarios 3 (conservative/base/aggressive)
special_indices 6 (launch_window/product_void/cannibalization/competitor_strength/artificial_demand/cost_of_error)
product_tz full grid (rank_segments × 15 ячеек)
recommendation_overlay full
COLD_BUILD_S 17.91s

Vs цель issue

до (исходный замер 2026-06-07) цель после forecast_cache
cold 215–233s (~3.5–4 мин) ≤ ~90s 17.9s
warm ~1s (анализирует analyse-1.0 кэш) без изменений

Цель перекрыта в 5×. UI больше не показывает «Прогноз рассчитывается…» 4 минуты — Celery-таска укладывается в ~20 сек.

Что НЕ делаем (и почему)

  • Параллелизация 4 горизонтов через ThreadPool. Sync SQLAlchemy Session не thread-safe → нужно per-thread SessionLocal() + переаудит детерминизма §16. Выгода ~18→5-7 сек ценой реальных рисков гонок и нарушения contextvar-скоупа forecast_cache. Не оправдано.
  • Precompute-прогрев демо-участков. Не требуется при cold ~20 сек.
  • Progress-индикатор в UI. Опционально — отдельный косметический issue по UX, если решат улучшить.

Закрываю

Цель issue достигнута до мерджа forecast_cache (PR #1160) сама по себе, без параллелизации. Replay-замер 2026-06-12 на самом тяжёлом prod-cad: 17.9 сек против цели «≤ 90 сек».

## Замер 2026-06-12 — цель достигнута через `forecast_cache` (PR #1160) `forecast_request_cache` (per-request memoization, мерж PR #1160 2026-06-08) сорвал амплификацию одних и тех же горизонт-инвариантных БД-загрузок: `get_monthly_macro` ×290 → ×1, `compute_market_metrics` ×124 → ×1, `get_competitors` ×69 → 1-на-горизонт, аналогично §9.4/§9.5/§9.6. Ничего не параллелим — выгрызаем дубликаты на одном потоке. ### Honest cold-cache замер на проде `docker exec gendesign-backend-1 python` → `build_site_finder_report(...)` напрямую (мимо Celery, без переиспользования таблицы analysis_runs): | параметр | значение | |---|---| | cad_num | `66:41:0304011:1124` (самый тяжёлый в анамнезе: 100 ранов) | | district | `None` (ЕКБ-wide — широчайший pipeline) | | horizons | 4 (6/12/18/24) | | scenarios | 3 (conservative/base/aggressive) | | special_indices | 6 (launch_window/product_void/cannibalization/competitor_strength/artificial_demand/cost_of_error) | | product_tz | full grid (rank_segments × 15 ячеек) | | recommendation_overlay | full | | **COLD_BUILD_S** | **17.91s** | ### Vs цель issue | | до (исходный замер 2026-06-07) | цель | после `forecast_cache` | |---|---|---|---| | cold | 215–233s (~3.5–4 мин) | ≤ ~90s | **17.9s** | | warm | ~1s (анализирует analyse-1.0 кэш) | — | без изменений | **Цель перекрыта в 5×.** UI больше не показывает «Прогноз рассчитывается…» 4 минуты — Celery-таска укладывается в ~20 сек. ### Что НЕ делаем (и почему) - **Параллелизация 4 горизонтов через ThreadPool.** Sync SQLAlchemy Session не thread-safe → нужно per-thread `SessionLocal()` + переаудит детерминизма §16. Выгода ~18→5-7 сек ценой реальных рисков гонок и нарушения contextvar-скоупа `forecast_cache`. Не оправдано. - **Precompute-прогрев демо-участков.** Не требуется при cold ~20 сек. - **Progress-индикатор в UI.** Опционально — отдельный косметический issue по UX, если решат улучшить. ### Закрываю Цель issue достигнута до мерджа `forecast_cache` (PR #1160) сама по себе, без параллелизации. Replay-замер 2026-06-12 на самом тяжёлом prod-cad: **17.9 сек** против цели «≤ 90 сек».
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#1129
No description provided.