All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m54s
CI / backend-tests (pull_request) Successful in 17m0s
11 тасок объявляли `max_retries=2`, но ретраи не реализовывали: ни `autoretry_for` в декораторе, ни вызова `self.retry()` в теле. Celery в таком виде параметр не применяет — при исключении таска падает с первой попытки. Читающий код видит «до 3 попыток», а их одна. Убран `max_retries` у: cbr_macro_sync, rosstat_macro_sync, developer_registry_refresh, location_refresh, mv_sales_tracker_refresh, refresh_analytics, refresh_layout_velocity, refresh_quarter_price_index, scrape_objective.sync_objective_group, supply_layers_refresh, scrape_kn.scrape_kn_region. Заодно убран `bind=True` там, где `self` не использовался вовсе; в `scrape_kn_region` он оставлен — `self.request.id` пишется в kn_scrape_log. Не тронуты и не должны быть: `resume_kn_run` (max_retries=12 + настоящий self.retry()), `nspd_sync`/`scrape_cadastre` (autoretry_for), `nspd_geo`/`objective_etl` (max_retries=0 — честное «ретраев нет»). Гейт `test_2464_retry_config_is_real.py` разбирает AST всех модулей `app/workers/tasks/` и требует: если декоратор объявляет ненулевой max_retries, в нём есть autoretry_for либо в теле функции есть self.retry(). Три таски из одиннадцати гейт нашёл сверх списка эпика. Проверка гейта: с фиксом зелено, при возврате `max_retries=2` в supply_layers_refresh — красно с указанием на эту таску. Плюс два контроля: гейт видит ≥20 тасок (не молчит из-за пустой выборки) и признаёт обе законные формы ретраев. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
61 lines
3 KiB
Python
61 lines
3 KiB
Python
"""Celery task: refresh developer_registry materialized view (#1088 «GG-форсайт»).
|
||
|
||
Вызывает ``fn_refresh_developer_registry()`` (миграция 149,
|
||
``data/sql/149_developer_attribution.sql``) — REFRESH MATERIALIZED VIEW CONCURRENTLY
|
||
``developer_registry`` (реестр застройщиков по норм-ИНН: канон-имя + track-record
|
||
permits+domrf). CONCURRENTLY не блокирует SELECT-читателей (analyze) во время refresh.
|
||
|
||
Scheduled via hardcoded beat entry в workers/beat_schedule.py:
|
||
'developer-registry-refresh-monthly' — ежемесячно 1-го числа в 05:30 МСК,
|
||
через 30 мин после 'ekburg-permits-monthly' (05:00 МСК), чтобы permits-сторона
|
||
реестра обновлялась на свежих РНС/РВЭ. domrf-сторона обновляется ежедневно
|
||
(scrape_kn), но ИНН/track-record застройщиков меняются редко — monthly refresh
|
||
реестра с запасом достаточно (резолвер всё равно читает domrf-quarter напрямую).
|
||
|
||
Issue: #1088.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
from typing import Any
|
||
|
||
from sqlalchemy import text
|
||
|
||
from app.core.db import SessionLocal
|
||
from app.workers.celery_app import celery_app
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
# Ретраев здесь нет, и параметров, обещающих их, быть не должно (#2464). Стояло
|
||
# `bind=True, max_retries=2`, но self не использовался, self.retry() не вызывался и
|
||
# autoretry_for задан не был — конфигурация не имела эффекта. Соседи, где ретраи
|
||
# нужны, задают их явно: autoretry_for (nspd_sync, scrape_cadastre) или self.retry()
|
||
# (scrape_kn).
|
||
@celery_app.task(
|
||
name="tasks.developer_registry_refresh.refresh_developer_registry",
|
||
)
|
||
def refresh_developer_registry() -> dict[str, Any]:
|
||
"""REFRESH MATERIALIZED VIEW CONCURRENTLY developer_registry.
|
||
|
||
Лёгкая задача (реестр ~1024 застройщика). CONCURRENTLY — non-blocking для
|
||
читателей analyze; требует unique-индекс uq_developer_registry_inn (создан
|
||
миграцией 149). Возвращает row-count реестра после refresh для observability.
|
||
|
||
Returns:
|
||
dict для Celery result store / логирования.
|
||
"""
|
||
db = SessionLocal()
|
||
try:
|
||
db.execute(text("SELECT fn_refresh_developer_registry()"))
|
||
db.commit()
|
||
row = db.execute(text("SELECT COUNT(*) FROM developer_registry")).first()
|
||
count = int(row[0]) if row else 0
|
||
logger.info("refresh_developer_registry: completed, developers=%d", count)
|
||
return {"status": "ok", "developer_registry_rows": count}
|
||
except Exception as e:
|
||
logger.exception("refresh_developer_registry failed: %s", e)
|
||
raise
|
||
finally:
|
||
db.close()
|