fix(beat): distinguish DB-unreachable from intentional-empty schedule (#520)
All checks were successful
Deploy / build-backend (push) Successful in 1m26s
Deploy / deploy (push) Successful in 1m4s
Deploy / changes (push) Successful in 5s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 2m35s

This commit is contained in:
lekss361 2026-05-24 13:14:11 +00:00
parent d9c6b1bd01
commit 44cb5b509d

View file

@ -53,16 +53,28 @@ def _default_regions() -> list[int]:
def _build_beat_schedule_from_db() -> dict:
"""Строит beat_schedule из job_settings БД.
Возвращает пустой словарь при любой ошибке caller добавит fallback.
"""
try:
from app.core.db import SessionLocal
from app.services.job_settings import get_all_safe
Returns: schedule (может быть пустой если все enabled rows без cron или disabled).
rows = get_all_safe(SessionLocal)
except Exception as e:
logger.warning("build_beat_schedule: не удалось прочитать job_settings: %s", e)
return {}
`get_all` маскирует DB-unreachable case через возврат _DEFAULTS (4 synthetic
rows, все enabled=True с дефолтными cron'ами) — это значит "DB unreachable" и
"fresh install (table empty)" неотличимы от "normal с _DEFAULTS". В обоих
случаях schedule НЕ будет пустым _DEFAULTS дадут scrape_kn + objective_sync.
КРИТИЧНО для operator-disable scenario: когда DB reachable и оператор сделал
`UPDATE job_settings SET enabled=false WHERE job_type IN (...)` get_all
вернёт реальные rows (НЕ _DEFAULTS), loop их пропустит schedule={}. Caller
(build_beat_schedule) НЕ должен срывался на env fallback в этом случае,
иначе disable бесполезен. См. инцидент 2026-05-24 (WAF cooldown disable
scrape_kn old code fallback'ал scrape_kn обратно из env).
"""
from app.core.db import SessionLocal
from app.services.job_settings import get_all
db = SessionLocal()
try:
rows = get_all(db)
finally:
db.close()
schedule: dict = {}
for row in rows:
@ -168,19 +180,18 @@ def _build_beat_schedule_fallback() -> dict:
def build_beat_schedule() -> dict:
"""Строит beat_schedule: сначала из DB, при неудаче — fallback на env.
"""Строит beat_schedule из DB (job_settings) + добавляет hardcoded entries.
Всегда добавляет refresh-ekb-districts-medians (нет в job_settings).
`_build_beat_schedule_from_db` использует `get_all` который сам fallback'ает на
_DEFAULTS при DB unreachable / fresh install возвращает 4 synthetic rows
(scrape_kn + objective_sync enabled). То есть schedule из DB-builder ПУСТ
только когда оператор явно disabled все cron-able jobs через UPDATE
job_settings это **намерение**, респектим без env-fallback.
Hardcoded entries (refresh-analytics, OSM POI/noise, nspd cleanup) добавляются
ВСЕГДА они не управляются через job_settings.
"""
schedule: dict = {}
try:
schedule = _build_beat_schedule_from_db()
except Exception as e:
logger.error("build_beat_schedule: неожиданная ошибка: %s — fallback", e)
if not schedule:
# БД пустая или недоступна — полный fallback
return _build_beat_schedule_fallback()
schedule = _build_beat_schedule_from_db()
# Всегда добавляем refresh_analytics (этот job нет в job_settings,
# он технический и не требует конфигурации через UI).