diff --git a/backend/routers/utility.py b/backend/routers/utility.py index 218e5552..5a61d43b 100644 --- a/backend/routers/utility.py +++ b/backend/routers/utility.py @@ -1,9 +1,9 @@ -from fastapi import APIRouter, Depends, HTTPException, Query -from fastapi.responses import JSONResponse +from fastapi import APIRouter, Depends, HTTPException, Query, Response from sqlalchemy.orm import Session from sqlalchemy import func, case from datetime import datetime, timezone import logging +import json from backend.database import get_db from backend.models import Issue @@ -51,7 +51,7 @@ def health(): def get_stats(db: Session = Depends(get_db)): cached_stats = recent_issues_cache.get("stats") if cached_stats: - return JSONResponse(content=cached_stats) + return Response(content=cached_stats, media_type="application/json") # Optimized: Single aggregate query for both category breakdowns and system-wide totals # This eliminates a redundant database roundtrip @@ -84,9 +84,10 @@ def get_stats(db: Session = Depends(get_db)): ) data = response.model_dump(mode='json') - recent_issues_cache.set(data, "stats") + json_data = json.dumps(data) + recent_issues_cache.set(json_data, "stats") - return response + return Response(content=json_data, media_type="application/json") @router.get("/ml-status", response_model=MLStatusResponse) async def ml_status(): @@ -116,7 +117,7 @@ def get_leaderboard(db: Session = Depends(get_db)): cache_key = "leaderboard" cached_data = recent_issues_cache.get(cache_key) if cached_data: - return JSONResponse(content=cached_data) + return Response(content=cached_data, media_type="application/json") # Group by user_email, count issues, sum upvotes # Optimization: Only select needed columns and use aggregation @@ -149,10 +150,11 @@ def get_leaderboard(db: Session = Depends(get_db)): ).model_dump(mode='json')) response_data = {"leaderboard": leaderboard_data} + json_data = json.dumps(response_data) # Cache for 5 minutes to reduce DB load on frequent hits - recent_issues_cache.set(response_data, cache_key) + recent_issues_cache.set(json_data, cache_key) - return response_data + return Response(content=json_data, media_type="application/json") @router.get("/mh/rep-contacts") diff --git a/backend/tests/benchmark_serialization.py b/backend/tests/benchmark_serialization.py new file mode 100644 index 00000000..ca724f1c --- /dev/null +++ b/backend/tests/benchmark_serialization.py @@ -0,0 +1,26 @@ +import time +import json +from fastapi import Response +from fastapi.responses import JSONResponse + +data = {"leaderboard": [{"user_email": "abc@def.com", "reports_count": 10, "total_upvotes": 50, "rank": 1} for _ in range(100)]} +json_data = json.dumps(data) + +def test_jsonresponse(): + start = time.perf_counter() + for _ in range(10000): + # JSONResponse internally calls json.dumps + resp = JSONResponse(content=data) + _ = resp.body + return time.perf_counter() - start + +def test_rawresponse(): + start = time.perf_counter() + for _ in range(10000): + resp = Response(content=json_data, media_type="application/json") + _ = resp.body + return time.perf_counter() - start + +if __name__ == "__main__": + print(f"JSONResponse: {test_jsonresponse():.4f}s") + print(f"Response with pre-serialized JSON: {test_rawresponse():.4f}s")