穩定成長分析共享快取
All checks were successful
CD Pipeline / deploy (push) Successful in 1m2s

This commit is contained in:
OoO
2026-05-19 12:44:17 +08:00
parent 45ae7a3d88
commit 8d0c442bdd
4 changed files with 100 additions and 7 deletions

View File

@@ -7,7 +7,10 @@ Sales/dashboard cache state lives in services.cache_manager. This module keeps
older imports working and only owns growth-analysis and slow-query helpers.
"""
import os
import pickle
from datetime import datetime, timezone, timedelta
from pathlib import Path
from services.cache_manager import (
_SALES_DF_CACHE,
_SALES_PROCESSED_CACHE,
@@ -37,6 +40,8 @@ _GROWTH_ANALYSIS_CACHE = {
'source_fingerprint': None,
}
_GROWTH_CACHE_TTL = 1800 # 成長分析快取: 30 分鐘
_BASE_DIR = Path(__file__).resolve().parents[1]
_GROWTH_SHARED_CACHE_FILE = _BASE_DIR / "data" / "growth_analysis_cache.pkl"
# ==========================================
# 慢查詢監控
@@ -84,6 +89,11 @@ def clear_growth_cache():
'timestamp': None,
'source_fingerprint': None,
}
try:
if os.path.exists(_GROWTH_SHARED_CACHE_FILE):
os.remove(_GROWTH_SHARED_CACHE_FILE)
except OSError:
pass
def clear_all_cache():
@@ -118,21 +128,66 @@ def get_growth_cache():
return _GROWTH_ANALYSIS_CACHE
def _is_growth_payload_valid(payload, source_fingerprint=None):
if not payload:
return False
if not payload.get('chart_data') or not payload.get('kpi'):
return False
if not is_cache_valid(payload.get('timestamp'), _GROWTH_CACHE_TTL):
return False
if source_fingerprint is not None:
return payload.get('source_fingerprint') == source_fingerprint
return True
def _load_growth_cache_file(source_fingerprint=None):
"""讀取跨 worker 成長分析快取,避免冷 worker 重新掃明細表。"""
global _GROWTH_ANALYSIS_CACHE
try:
if not os.path.exists(_GROWTH_SHARED_CACHE_FILE):
return False
with open(_GROWTH_SHARED_CACHE_FILE, 'rb') as f:
payload = pickle.load(f)
if not _is_growth_payload_valid(payload, source_fingerprint):
return False
_GROWTH_ANALYSIS_CACHE = payload
return True
except Exception:
return False
def _write_growth_cache_file(payload):
"""原子寫入成長分析共享快取。"""
cache_file = str(_GROWTH_SHARED_CACHE_FILE)
tmp_file = f"{cache_file}.{os.getpid()}.tmp"
try:
os.makedirs(os.path.dirname(cache_file), exist_ok=True)
with open(tmp_file, 'wb') as f:
pickle.dump(payload, f, protocol=pickle.HIGHEST_PROTOCOL)
os.replace(tmp_file, cache_file)
except Exception:
try:
if os.path.exists(tmp_file):
os.remove(tmp_file)
except OSError:
pass
def set_growth_cache(chart_data, kpi, source_fingerprint=None):
"""設定成長分析快取"""
global _GROWTH_ANALYSIS_CACHE
_GROWTH_ANALYSIS_CACHE = {
payload = {
'chart_data': chart_data,
'kpi': kpi,
'timestamp': datetime.now(TAIPEI_TZ),
'source_fingerprint': source_fingerprint,
}
_GROWTH_ANALYSIS_CACHE = payload
_write_growth_cache_file(payload)
def is_growth_cache_valid(source_fingerprint=None):
"""檢查成長分析快取是否有效"""
if not is_cache_valid(_GROWTH_ANALYSIS_CACHE.get('timestamp'), _GROWTH_CACHE_TTL):
return False
if source_fingerprint is not None:
return _GROWTH_ANALYSIS_CACHE.get('source_fingerprint') == source_fingerprint
return True
if _is_growth_payload_valid(_GROWTH_ANALYSIS_CACHE, source_fingerprint):
return True
return _load_growth_cache_file(source_fingerprint)