From 2fe1dcf4da62184e06921c9a1a12e0ae1ff770bc Mon Sep 17 00:00:00 2001 From: grabowski Date: Sun, 9 Aug 2026 17:15:47 +0700 Subject: [PATCH] feat: add 5-minute TTL cache for PostgreSQL history queries --- src/web_api.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/web_api.py b/src/web_api.py index ab51337..3c25695 100644 --- a/src/web_api.py +++ b/src/web_api.py @@ -5,8 +5,10 @@ FastAPI web interface for water monitoring system import asyncio import os +import time from contextlib import asynccontextmanager from datetime import datetime, timedelta +from threading import Lock from typing import Any, Dict, List import requests @@ -34,6 +36,11 @@ from .water_scraper_v3 import EnhancedWaterMonitorScraper logger = get_logger(__name__) +# Simple thread-safe TTL cache for PostgreSQL history queries +HISTORY_CACHE: Dict[str, tuple] = {} +HISTORY_CACHE_LOCK = Lock() +HISTORY_TTL = 300 # 5 minutes + # Dashboard HTML is loaded once at import from src/static/dashboard.html. _DASHBOARD_HTML_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static", "dashboard.html") try: @@ -448,19 +455,28 @@ async def get_postgres_history( limit: int = Query(2000, ge=1, le=5000), ): """Get historical measurements for a station from PostgreSQL.""" + cache_key = f"{station_code}:{hours}:{limit}" + now = time.monotonic() + with HISTORY_CACHE_LOCK: + cached = HISTORY_CACHE.get(cache_key) + if cached and now - cached[0] < HISTORY_TTL: + return cached[1] try: db_config = Config.get_database_config() if db_config["type"] != "postgresql": raise RuntimeError("PostgreSQL is not configured") history = PostgresHistory(db_config["connection_string"]) end_time = datetime.now() - return await asyncio.to_thread( + data = await asyncio.to_thread( history.station_history, station_code, end_time - timedelta(hours=hours), end_time, limit, ) + with HISTORY_CACHE_LOCK: + HISTORY_CACHE[cache_key] = (now, data) + return data except RuntimeError as error: raise HTTPException(status_code=503, detail=str(error)) except Exception as error: