perf: cache public activity aggregates

This commit is contained in:
ik
2026-09-07 18:54:58 +07:00
parent 24ee01b9c3
commit 214b328cd2
10 changed files with 64 additions and 4 deletions
+13 -2
View File
@@ -26,6 +26,7 @@ from .importer import ImportAlreadyRunning, ImportSourceError, import_records, n
from .logging_config import configure_logging
from .models import Bait, BaitKind, CatchReport, CommunityImportRun, DataSource, ExternalObservation, Fish, ModerationEvent, ModerationStatus, OfficialRecordImport, SourceType, Spot, SubmissionAttempt, Waterbody
from .readiness import readiness_report
from .public_cache import public_cache
from .schemas import ActivityOut, AdminCatchReportOut, BaitOut, CatchOut, CatchReportAccepted, CatchReportCreate, CatchReportCreated, ExternalObservationDecision, ExternalObservationMapping, ExternalObservationOut, ExternalObservationPublished, FishOut, ImportRunOut, ModerationUpdate, OfficialRecordOut, PublicObservationOut, SourceStatusOut, SpotOut, WaterbodyOut
from .storage import ScreenshotError, client as storage_client, delete_screenshot, signed_screenshot_url, upload_screenshot
@@ -110,7 +111,7 @@ def baits(db: Db, limit: int = Query(200, ge=1, le=500), offset: int = Query(0,
@app.get("/api/v1/activity", response_model=list[ActivityOut])
def activity(
db: Db, hours: int = Query(24),
db: Db, response: Response, hours: int = Query(24),
waterbody: str | None = None, fish: str | None = None,
method: str | None = None,
sort: Literal["activity", "confidence", "freshness"] = "activity",
@@ -118,6 +119,12 @@ def activity(
) -> list[ActivityOut]:
if hours not in {6, 12, 24, 72}:
raise HTTPException(status_code=422, detail="hours must be one of: 6, 12, 24, 72")
response.headers["Cache-Control"] = f"public, max-age={settings.public_cache_seconds}"
cache_key = ("activity", hours, waterbody, fish, method, sort, limit, offset)
cached = public_cache.get(cache_key, settings.public_cache_seconds)
if cached is not None:
response.headers["X-Cache"] = "HIT"
return cached
rows = activity_rows(db, hours=hours, waterbody=waterbody, fish=fish, method=method)
keys = {
"activity": lambda r: (r.activity_score, r.confidence_score, r.last_confirmed_at, str(r.spot_id)),
@@ -125,7 +132,8 @@ def activity(
"freshness": lambda r: (r.last_confirmed_at, r.activity_score, r.confidence_score, str(r.spot_id)),
}
rows.sort(key=keys[sort], reverse=True)
return rows[offset:offset + limit]
response.headers["X-Cache"] = "MISS"
return public_cache.set(cache_key, rows[offset:offset + limit])
def _spot_or_404(db: Session, spot_id: UUID) -> Spot:
@@ -372,6 +380,7 @@ def admin_publish_external_observation(
report = publish_observation(db, observation)
except ExternalReviewError as exc:
raise HTTPException(status_code=409, detail=str(exc)) from exc
public_cache.invalidate()
return ExternalObservationPublished(observation_id=observation.id, catch_report_id=report.id, status=observation.status)
@@ -454,6 +463,7 @@ def moderate_report(report_id: UUID, payload: ModerationUpdate, db: Db, moderato
report.moderation_status = ModerationStatus(payload.status)
db.add(ModerationEvent(catch_report=report, created_at=datetime.now(timezone.utc), previous_status=previous, new_status=report.moderation_status, moderator=moderator, reason=payload.reason))
db.commit()
public_cache.invalidate()
return CatchReportCreated(id=report.id, moderation_status=report.moderation_status.value)
@@ -476,6 +486,7 @@ def delete_report(report_id: UUID, db: Db, moderator: Annotated[str, Depends(_ad
report.raw_payload = None
db.add(ModerationEvent(catch_report=report, created_at=report.deleted_at, previous_status=previous, new_status=ModerationStatus.rejected, moderator=moderator, reason="user report deleted and anonymized"))
db.commit()
public_cache.invalidate()
return Response(status_code=204)