Optimize multi-model caching and frontend revalidation

This commit is contained in:
2569718930@qq.com
2026-05-20 08:58:16 +08:00
parent 1af33c3ab8
commit 72e93f8e93
7 changed files with 78 additions and 6 deletions
+1 -1
View File
@@ -860,7 +860,7 @@ export function DashboardStoreProvider({
void (async () => {
try {
const latestDetail = await dashboardClient.getCityDetail(cityName, {
force: true,
force: false,
depth,
});
const detail = latestDetail;
+2 -2
View File
@@ -518,8 +518,8 @@ export const dashboardClient = {
meta: Record<string, CityCacheMeta>,
) {
if (!isClient()) return;
// Keep only the 3 most-recently-accessed cities to prevent sessionStorage bloat
const MAX_CACHED_CITIES = 3;
// Keep only the 12 most-recently-accessed cities to prevent sessionStorage bloat
const MAX_CACHED_CITIES = 12;
const allEntries = Object.entries(details).map(([cityName, detail]) => ({
cityName,
cachedAt: meta[cityName]?.cachedAt || 0,
+12
View File
@@ -307,6 +307,16 @@ class DBManager:
source_fingerprint TEXT
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS city_full_cache (
city TEXT PRIMARY KEY,
payload_json TEXT NOT NULL,
updated_at TEXT NOT NULL,
updated_at_ts REAL NOT NULL,
version TEXT,
source_fingerprint TEXT
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS cache_refresh_locks (
cache_key TEXT PRIMARY KEY,
@@ -432,6 +442,8 @@ class DBManager:
return "city_nearby_cache"
if normalized == "market":
return "city_market_cache"
if normalized == "full":
return "city_full_cache"
return None
def get_city_cache(self, kind: str, city: str) -> Optional[Dict[str, Any]]:
+33
View File
@@ -0,0 +1,33 @@
import tempfile
import os
from src.database.db_manager import DBManager
def test_full_cache_lifecycle():
with tempfile.TemporaryDirectory() as tmpdir:
db_path = os.path.join(tmpdir, "test.db")
if hasattr(DBManager, "_initialized_paths"):
DBManager._initialized_paths.clear()
db = DBManager(db_path)
# Verify table name resolution
assert db._cache_table_name("full") == "city_full_cache"
# Verify cache save and load lifecycle
city = "testcity"
payload = {"data": "test_payload_full_depth", "nested": {"key": 123}}
db.set_city_cache(
kind="full",
city=city,
payload=payload,
version="v1",
source_fingerprint="testcity:full"
)
cached = db.get_city_cache("full", city)
assert cached is not None
assert cached["city"] == city
assert cached["payload"] == payload
assert cached["version"] == "v1"
assert cached["source_fingerprint"] == "testcity:full"
+9
View File
@@ -79,6 +79,15 @@ async def get_city_detail_payload(
detail_mode = "nearby"
else:
detail_mode = "panel"
if detail_mode == "full":
if force_refresh:
return await run_in_threadpool(legacy_routes._refresh_city_full_cache, city, True)
cached_entry = await run_in_threadpool(legacy_routes._CACHE_DB.get_city_cache, "full", city)
if cached_entry:
if not legacy_routes._city_cache_is_fresh(cached_entry, legacy_routes.CITY_FULL_CACHE_TTL_SEC):
return await run_in_threadpool(legacy_routes._refresh_city_full_cache, city, False)
return cached_entry.get("payload") or {}
return await run_in_threadpool(legacy_routes._refresh_city_full_cache, city, False)
if detail_mode == "panel":
if force_refresh:
return await run_in_threadpool(legacy_routes._refresh_city_panel_cache, city, True)
+17 -2
View File
@@ -142,6 +142,7 @@ CITY_SUMMARY_CACHE_TTL_SEC = max(30, int(os.getenv("POLYWEATHER_CITY_SUMMARY_CAC
CITY_PANEL_CACHE_TTL_SEC = max(30, int(os.getenv("POLYWEATHER_CITY_PANEL_CACHE_TTL_SEC", "1800")))
CITY_NEARBY_CACHE_TTL_SEC = max(30, int(os.getenv("POLYWEATHER_CITY_NEARBY_CACHE_TTL_SEC", "1800")))
CITY_MARKET_CACHE_TTL_SEC = max(30, int(os.getenv("POLYWEATHER_CITY_MARKET_CACHE_TTL_SEC", "1800")))
CITY_FULL_CACHE_TTL_SEC = max(30, int(os.getenv("POLYWEATHER_CITY_FULL_CACHE_TTL_SEC", "1800")))
MARKET_SCAN_PAYLOAD_TTL_SEC = max(
5,
int(os.getenv("POLYWEATHER_MARKET_SCAN_PAYLOAD_TTL_SEC", "30")),
@@ -319,6 +320,18 @@ def _refresh_city_market_cache(city: str, force_refresh: bool = False) -> dict:
return payload
def _refresh_city_full_cache(city: str, force_refresh: bool = False) -> dict:
payload = _analyze(city, force_refresh=force_refresh, include_llm_commentary=True, detail_mode="full")
_CACHE_DB.set_city_cache(
"full",
city,
payload,
version="v1",
source_fingerprint=f"{city}:full",
)
return payload
def _schedule_cache_refresh(
background_tasks: BackgroundTasks,
@@ -329,7 +342,7 @@ def _schedule_cache_refresh(
) -> bool:
normalized_kind = str(kind or "").strip().lower()
normalized_city = str(city or "").strip().lower()
if normalized_kind not in {"summary", "panel", "nearby", "market"} or not normalized_city:
if normalized_kind not in {"summary", "panel", "nearby", "market", "full"} or not normalized_city:
return False
cache_key = f"city:{normalized_kind}:{normalized_city}"
owner = _CACHE_DB.acquire_cache_refresh_lock(
@@ -347,8 +360,10 @@ def _schedule_cache_refresh(
_refresh_city_panel_cache(normalized_city, force_refresh=force_refresh)
elif normalized_kind == "nearby":
_refresh_city_nearby_cache(normalized_city, force_refresh=force_refresh)
else:
elif normalized_kind == "market":
_refresh_city_market_cache(normalized_city, force_refresh=force_refresh)
else:
_refresh_city_full_cache(normalized_city, force_refresh=force_refresh)
except Exception as exc:
logger.warning(
"cache refresh failed kind={} city={} force_refresh={}: {}",
+4 -1
View File
@@ -35,7 +35,9 @@ def get_system_cache_status(request: Request, cities: Optional[str] = None) -> D
"summary": legacy_routes.CITY_SUMMARY_CACHE_TTL_SEC,
"panel": legacy_routes.CITY_PANEL_CACHE_TTL_SEC,
"nearby": legacy_routes.CITY_NEARBY_CACHE_TTL_SEC,
"market": legacy_routes.CITY_MARKET_CACHE_TTL_SEC, }
"market": legacy_routes.CITY_MARKET_CACHE_TTL_SEC,
"full": legacy_routes.CITY_FULL_CACHE_TTL_SEC,
}
items = []
for city in selected:
row = {"city": city}
@@ -71,6 +73,7 @@ def run_system_priority_warm(
legacy_routes._refresh_city_panel_cache(city, force_refresh=False)
legacy_routes._refresh_city_nearby_cache(city, force_refresh=False)
legacy_routes._refresh_city_market_cache(city, force_refresh=False)
legacy_routes._refresh_city_full_cache(city, force_refresh=False)
except Exception as exc:
logger.warning("priority warm primary failed city={} timezone={}: {}", city, timezone, exc)
for city in secondary: