fix(workers): schedule mv_layout_velocity refresh in Celery beat (#1666)
All checks were successful
CI / backend-tests (push) Successful in 8m49s
CI / backend-tests (pull_request) Successful in 8m52s
CI / changes (push) Successful in 6s
CI / frontend-tests (push) Has been skipped
CI / changes (pull_request) Successful in 8s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (push) Successful in 1m57s
CI / openapi-codegen-check (pull_request) Successful in 1m55s

mv_layout_velocity (powers best_layouts, #113) refreshed only via manual
refresh_layout_velocity() → prod data silently went stale. Add Celery task
mirroring refresh_quarter_price_index, register in celery_app include, add
weekly beat entry (Sun 03:00 MSK, outside the monday heavy-refresh cluster).

Closes #1666
This commit is contained in:
Light1YT 2026-06-17 18:40:01 +05:00
parent e7901bc1e8
commit 7276edc885
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()