ADR-017 Phase 3f-2:新增 services/cache_manager.py,讓 sales/import/export/daily/dashboard 共用同一份 in-memory cache;cache_service 改為相容 shim;Dockerfile/docker-compose 啟用 gunicorn --preload。
130 lines
3.5 KiB
Python
130 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
快取服務模組
|
|
集中管理所有快取變數和相關操作
|
|
"""
|
|
|
|
from datetime import datetime, timezone, timedelta
|
|
from services.cache_manager import (
|
|
_SALES_DF_CACHE,
|
|
_SALES_PROCESSED_CACHE,
|
|
_SALES_OPTIONS_CACHE,
|
|
_SALES_ANALYSIS_RESULT_CACHE,
|
|
_DASHBOARD_DATA_CACHE,
|
|
_DASHBOARD_CACHE_TTL,
|
|
clear_sales_cache,
|
|
clear_dashboard_cache,
|
|
)
|
|
|
|
# 台北時區
|
|
TAIPEI_TZ = timezone(timedelta(hours=8))
|
|
|
|
# 快取 TTL 設定
|
|
_SALES_CACHE_TTL = 3600 # 業績分析快取: 60 分鐘
|
|
_SALES_OPTIONS_TTL = 21600 # 選項快取: 6 小時
|
|
_SALES_RESULT_TTL = 3600 # 結果快取: 60 分鐘
|
|
|
|
# ==========================================
|
|
# 成長分析快取
|
|
# ==========================================
|
|
_GROWTH_ANALYSIS_CACHE = {
|
|
'chart_data': None,
|
|
'kpi': None,
|
|
'timestamp': None
|
|
}
|
|
_GROWTH_CACHE_TTL = 1800 # 成長分析快取: 30 分鐘
|
|
|
|
# ==========================================
|
|
# 慢查詢監控
|
|
# ==========================================
|
|
_SLOW_QUERY_STATS = {
|
|
'total_queries': 0,
|
|
'slow_queries': 0,
|
|
'very_slow_queries': 0,
|
|
'total_query_time_ms': 0,
|
|
'last_slow_query': None,
|
|
'last_slow_query_time': None,
|
|
}
|
|
_SLOW_QUERY_THRESHOLD_MS = 1000 # 慢查詢閾值: 1秒
|
|
_VERY_SLOW_QUERY_THRESHOLD_MS = 5000 # 極慢查詢閾值: 5秒
|
|
|
|
|
|
def track_query_time(query_name, duration_ms):
|
|
"""追蹤查詢時間,更新慢查詢統計"""
|
|
global _SLOW_QUERY_STATS
|
|
_SLOW_QUERY_STATS['total_queries'] += 1
|
|
_SLOW_QUERY_STATS['total_query_time_ms'] += duration_ms
|
|
|
|
if duration_ms >= _VERY_SLOW_QUERY_THRESHOLD_MS:
|
|
_SLOW_QUERY_STATS['very_slow_queries'] += 1
|
|
_SLOW_QUERY_STATS['slow_queries'] += 1
|
|
_SLOW_QUERY_STATS['last_slow_query'] = query_name
|
|
_SLOW_QUERY_STATS['last_slow_query_time'] = datetime.now(TAIPEI_TZ).isoformat()
|
|
elif duration_ms >= _SLOW_QUERY_THRESHOLD_MS:
|
|
_SLOW_QUERY_STATS['slow_queries'] += 1
|
|
_SLOW_QUERY_STATS['last_slow_query'] = query_name
|
|
_SLOW_QUERY_STATS['last_slow_query_time'] = datetime.now(TAIPEI_TZ).isoformat()
|
|
|
|
|
|
def get_slow_query_stats():
|
|
"""取得慢查詢統計資料"""
|
|
return _SLOW_QUERY_STATS.copy()
|
|
|
|
|
|
def clear_growth_cache():
|
|
"""清除成長分析快取"""
|
|
global _GROWTH_ANALYSIS_CACHE
|
|
_GROWTH_ANALYSIS_CACHE = {
|
|
'chart_data': None,
|
|
'kpi': None,
|
|
'timestamp': None
|
|
}
|
|
|
|
|
|
def clear_all_cache():
|
|
"""清除所有快取"""
|
|
clear_sales_cache()
|
|
clear_dashboard_cache()
|
|
clear_growth_cache()
|
|
|
|
|
|
def is_cache_valid(timestamp, ttl_seconds):
|
|
"""
|
|
檢查快取是否有效
|
|
|
|
Args:
|
|
timestamp: 快取時間戳記
|
|
ttl_seconds: 快取有效期(秒)
|
|
|
|
Returns:
|
|
bool: True 表示快取有效
|
|
"""
|
|
if timestamp is None:
|
|
return False
|
|
now = datetime.now(TAIPEI_TZ)
|
|
if timestamp.tzinfo is None:
|
|
timestamp = timestamp.replace(tzinfo=TAIPEI_TZ)
|
|
age = (now - timestamp).total_seconds()
|
|
return age < ttl_seconds
|
|
|
|
|
|
def get_growth_cache():
|
|
"""取得成長分析快取"""
|
|
return _GROWTH_ANALYSIS_CACHE
|
|
|
|
|
|
def set_growth_cache(chart_data, kpi):
|
|
"""設定成長分析快取"""
|
|
global _GROWTH_ANALYSIS_CACHE
|
|
_GROWTH_ANALYSIS_CACHE = {
|
|
'chart_data': chart_data,
|
|
'kpi': kpi,
|
|
'timestamp': datetime.now(TAIPEI_TZ)
|
|
}
|
|
|
|
|
|
def is_growth_cache_valid():
|
|
"""檢查成長分析快取是否有效"""
|
|
return is_cache_valid(_GROWTH_ANALYSIS_CACHE.get('timestamp'), _GROWTH_CACHE_TTL)
|