fix(workers): schedule mv_layout_velocity refresh (#1666) #1669

Merged
lekss361 merged 1 commit from fix/1666-layout-velocity-beat into main 2026-06-17 17:12:55 +00:00
3 changed files with 64 additions and 0 deletions

View file

@ -506,4 +506,20 @@ def build_beat_schedule() -> dict:
"options": {"queue": "celery"},
}
# Refresh mv_layout_velocity (#1666) — питает «лучшие планировки» best_layouts (#113).
# До этого MV рефрешился ТОЛЬКО ручным вызовом refresh_layout_velocity() → в проде
# данные молча устаревали. REFRESH ... CONCURRENTLY (non-blocking) по mv_layout_velocity.
#
# Воскресенье 03:00 МСК (Celery conf.timezone=Europe/Moscow → crontab в МСК, #1233).
# Намеренно ВНЕ monday-кластера тяжёлых site_finder-рефрешей (scrape_kn 04:15,
# ird 05:00, gknspecial 05:30, supply-layers 06:00, genplan 06:30, location 07:00) и
# вне воскресного okn-objects (04:30) — час запаса, не конкурирует за БД/CPU.
# MV агрегирует темпы вымывания планировок (меняются медленно) → еженедельно хватает.
# Техническая infra-задача, не в job_settings (как refresh-quarter-price-index / supply-layers).
schedule["refresh-layout-velocity"] = {
"task": "tasks.refresh_layout_velocity.refresh_layout_velocity",
"schedule": _parse_cron("0 3 * * sun"), # 03:00 MSK, воскресенье
"options": {"queue": "celery"},
}
return schedule

View file

@ -79,6 +79,7 @@ celery_app = Celery(
"app.workers.tasks.pat_subzones_load",
"app.workers.tasks.izyatie_ocr_ingest",
"app.workers.tasks.developer_registry_refresh",
"app.workers.tasks.refresh_layout_velocity",
],
)
celery_app.conf.timezone = "Europe/Moscow"

View file

@ -0,0 +1,47 @@
"""Celery task: refresh mv_layout_velocity (питает «лучшие планировки» best_layouts).
Scheduled via hardcoded beat entry in workers/beat_schedule.py:
'refresh-layout-velocity' weekly on Sunday at 03:00 MSK.
Стоит в «ночном» окне воскресенья, отдельно от monday-кластера тяжёлых
site_finder-рефрешей (supply-layers / ird / gknspecial / cbr и т.д.), чтобы
не конкурировать с ними за БД/CPU.
Issue: #1666 (рефреш не был подключён в beat → данные best_layouts молча
устаревали в проде). MV-источник: #113 (PR B, mv_layout_velocity → best_layouts).
"""
from __future__ import annotations
import logging
from typing import Any
from app.core.db import SessionLocal
from app.services.site_finder.layout_velocity_refresh import refresh_layout_velocity
from app.workers.celery_app import celery_app
logger = logging.getLogger(__name__)
@celery_app.task(
bind=True,
name="tasks.refresh_layout_velocity.refresh_layout_velocity",
max_retries=2,
)
def refresh_layout_velocity_task(self: Any) -> dict[str, Any]:
"""REFRESH MATERIALIZED VIEW mv_layout_velocity (best_layouts, #113 / #1666).
MV рефрешится CONCURRENTLY (non-blocking, требует unique-индекс
mv_layout_velocity_pk); сервис сам падает в non-concurrent при unpopulated MV.
Returns result dict for Celery task result store / logging.
"""
db = SessionLocal()
try:
count = refresh_layout_velocity(db, concurrently=True)
logger.info("refresh_layout_velocity: completed, mv rows=%d", count)
return {"status": "ok", "mv_layout_velocity_rows": count}
except Exception as e:
logger.exception("refresh_layout_velocity failed: %s", e)
raise
finally:
db.close()