fix: defer daily report push и увеличить pool БД (0.4.12)

FCM/каналы для report.daily.* после commit ingest (BackgroundTasks);
SAC_DB_POOL_SIZE=15, SAC_DB_MAX_OVERFLOW=25 для штурма 09:00.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
PTah
2026-07-06 12:00:37 +10:00
parent 6c43b8637e
commit c72e510fb0
7 changed files with 37 additions and 5 deletions
+8 -2
View File
@@ -2,7 +2,7 @@
from datetime import datetime, timezone
from typing import Any
from fastapi import APIRouter, Body, Depends, HTTPException, Query
from fastapi import APIRouter, BackgroundTasks, Body, Depends, HTTPException, Query
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from sqlalchemy import func, select
@@ -51,6 +51,7 @@ from app.services.notify_dispatch import (
notify_lifecycle,
notify_problem,
notify_rdg_connection,
schedule_notify_daily_report,
)
router = APIRouter(prefix="/events", tags=["events"])
@@ -79,6 +80,7 @@ def _ingest_response(event: Event, *, created: bool) -> IngestResponse:
@router.post("", response_model=IngestResponse)
def post_event(
background_tasks: BackgroundTasks,
payload: dict[str, Any] = Body(...),
db: Session = Depends(get_db),
_api_key: str = Depends(get_api_key_auth),
@@ -96,11 +98,12 @@ def post_event(
event, created = ingest_event(db, payload)
problem = None
problem_created = False
deferred_daily_report_id: int | None = None
if created:
problem, problem_created = maybe_create_problem(db, event)
maybe_auto_disconnect_stuck_rdp_session(db, event)
if event.type in DAILY_REPORT_EVENT_TYPES:
notify_daily_report(event, db=db)
deferred_daily_report_id = event.id
elif event.type == LIFECYCLE_EVENT_TYPE:
notify_lifecycle(event, db=db)
elif event.type in AUTH_LOGIN_SUCCESS_TYPES:
@@ -116,6 +119,9 @@ def post_event(
logger.info("ingest duplicate event_id=%s", event.event_id)
db.commit()
if deferred_daily_report_id is not None:
background_tasks.add_task(schedule_notify_daily_report, deferred_daily_report_id)
body = _ingest_response(event, created=created)
if problem is not None:
body.problem_id = problem.id
+2
View File
@@ -34,6 +34,8 @@ class Settings(BaseSettings):
model_config = _settings_config()
database_url: str = "postgresql+psycopg2://sac:sac@localhost:5432/sac"
sac_db_pool_size: int = 15
sac_db_max_overflow: int = 25
sac_public_url: str = "http://localhost:8000"
jwt_secret: str = "change-me-in-production"
jwt_algorithm: str = "HS256"
+5 -1
View File
@@ -6,7 +6,11 @@ from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
from app.config import get_settings
settings = get_settings()
engine = create_engine(settings.database_url, pool_pre_ping=True)
_engine_kwargs: dict = {"pool_pre_ping": True}
if settings.database_url.startswith("postgresql"):
_engine_kwargs["pool_size"] = max(1, int(settings.sac_db_pool_size))
_engine_kwargs["max_overflow"] = max(0, int(settings.sac_db_max_overflow))
engine = create_engine(settings.database_url, **_engine_kwargs)
SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False)
+17
View File
@@ -142,3 +142,20 @@ def notify_rdg_connection(event: Event, *, db: Session | None = None) -> None:
if not should_notify_event(event, db):
return
_dispatch_lifecycle_channels(event, db=db, policy=policy)
def schedule_notify_daily_report(event_db_id: int) -> None:
"""Отложенное оповещение по суточному отчёту (после commit ingest, вне горячего POST)."""
from app.database import SessionLocal
db = SessionLocal()
try:
event = db.get(Event, event_db_id)
if event is None:
logger.warning("deferred daily report notify: event id=%s not found", event_db_id)
return
notify_daily_report(event, db=db)
except Exception:
logger.exception("deferred daily report notify failed event_db_id=%s", event_db_id)
finally:
db.close()
+1 -1
View File
@@ -1,5 +1,5 @@
"""Единый источник версии SAC (API, health, логи, OpenAPI)."""
APP_NAME = "Security Alert Center"
APP_VERSION = "0.4.11"
APP_VERSION = "0.4.12"
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
+1 -1
View File
@@ -126,7 +126,7 @@ def test_ingest_daily_report_calls_notify_daily_report(client, auth_headers):
"summary": "stats",
"details": {"generated_by": "agent", "report_body": "line1"},
}
with patch.object(events_api, "notify_daily_report") as mock_daily:
with patch.object(events_api, "schedule_notify_daily_report") as mock_daily:
with patch.object(events_api, "notify_event") as mock_event:
r = client.post("/api/v1/events", json=payload, headers=auth_headers)
assert r.status_code == 201