mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
perf: 全栈查询优化、前端缓存去重与页面可见性优化
后端: - SQL count 查询统一改用 func.count() 子查询替代 query.count() - Dashboard/Audit 等页面多次独立查询合并为单次聚合查询 - Provider summary 列表改为批量查询消除 N+1 问题 - DailyStats 逐天循环查询改为 CASE 分桶单次查询 - 使用 load_only() 减少不必要的列加载 - cache_decorator 支持嵌套属性路径解析(dotted vary_by) - 多个管理/公共端点新增 @cache_result 缓存装饰 前端: - cache.ts 新增 in-flight 请求复用、dedupedRequest、buildCacheKey - 大量 API 调用添加前端缓存或去重 - 多个页面定时器在标签页隐藏时暂停、可见时恢复 - Auth 检查从 setInterval 改为 storage + visibilitychange 事件驱动 - 请求竞态防护(requestId 模式) 数据库: - Usage 表新增 idx_usage_status_user_created 复合索引
This commit is contained in:
@@ -10,9 +10,11 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from src.api.base.admin_adapter import AdminApiAdapter
|
||||
from src.api.base.context import ApiRequestContext
|
||||
from src.config.constants import CacheTTL
|
||||
from src.database import get_db
|
||||
from src.services.system.stats_aggregator import AggregatedStats, StatsFilter, query_stats_hybrid
|
||||
from src.services.system.time_range import TimeRangeParams
|
||||
from src.utils.cache_decorator import cache_result
|
||||
|
||||
from .common import pipeline
|
||||
|
||||
@@ -34,6 +36,18 @@ class AdminComparisonAdapter(AdminApiAdapter):
|
||||
self.timezone_name = timezone_name
|
||||
self.tz_offset_minutes = tz_offset_minutes
|
||||
|
||||
@cache_result(
|
||||
key_prefix="admin:stats:comparison",
|
||||
ttl=CacheTTL.ADMIN_USAGE_AGGREGATION,
|
||||
user_specific=False,
|
||||
vary_by=[
|
||||
"current_start",
|
||||
"current_end",
|
||||
"comparison_type",
|
||||
"timezone_name",
|
||||
"tz_offset_minutes",
|
||||
],
|
||||
)
|
||||
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
|
||||
if self.current_start > self.current_end:
|
||||
raise HTTPException(status_code=400, detail="current_start must be <= current_end")
|
||||
|
||||
@@ -11,10 +11,12 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from src.api.base.admin_adapter import AdminApiAdapter
|
||||
from src.api.base.context import ApiRequestContext
|
||||
from src.config.constants import CacheTTL
|
||||
from src.database import get_db
|
||||
from src.models.database import Usage
|
||||
from src.services.system.stats_aggregator import query_time_series
|
||||
from src.services.system.time_range import TimeRangeParams
|
||||
from src.utils.cache_decorator import cache_result
|
||||
|
||||
from .common import (
|
||||
_apply_admin_default_range,
|
||||
@@ -40,11 +42,27 @@ class AdminCostForecastAdapter(AdminApiAdapter):
|
||||
self.days = days
|
||||
self.forecast_days = forecast_days
|
||||
self.timezone_name = timezone_name
|
||||
self.tz_offset_minutes = tz_offset_minutes
|
||||
self.fallback_tz_offset_minutes = tz_offset_minutes
|
||||
|
||||
@cache_result(
|
||||
key_prefix="admin:stats:cost:forecast",
|
||||
ttl=CacheTTL.ADMIN_USAGE_AGGREGATION,
|
||||
user_specific=False,
|
||||
vary_by=[
|
||||
"time_range.start_date",
|
||||
"time_range.end_date",
|
||||
"time_range.preset",
|
||||
"time_range.timezone",
|
||||
"time_range.tz_offset_minutes",
|
||||
"days",
|
||||
"forecast_days",
|
||||
"timezone_name",
|
||||
"fallback_tz_offset_minutes",
|
||||
],
|
||||
)
|
||||
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
|
||||
time_range = self.time_range or _build_time_range_from_days(
|
||||
self.days, self.timezone_name, self.tz_offset_minutes
|
||||
self.days, self.timezone_name, self.fallback_tz_offset_minutes
|
||||
)
|
||||
time_range.granularity = "day"
|
||||
try:
|
||||
@@ -120,6 +138,20 @@ class AdminCostSavingsAdapter(AdminApiAdapter):
|
||||
self.provider_name = provider_name
|
||||
self.model = model
|
||||
|
||||
@cache_result(
|
||||
key_prefix="admin:stats:cost:savings",
|
||||
ttl=CacheTTL.ADMIN_USAGE_AGGREGATION,
|
||||
user_specific=False,
|
||||
vary_by=[
|
||||
"time_range.start_date",
|
||||
"time_range.end_date",
|
||||
"time_range.preset",
|
||||
"time_range.timezone",
|
||||
"time_range.tz_offset_minutes",
|
||||
"provider_name",
|
||||
"model",
|
||||
],
|
||||
)
|
||||
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
|
||||
if not self.time_range:
|
||||
return {
|
||||
|
||||
@@ -11,9 +11,11 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from src.api.base.admin_adapter import AdminApiAdapter
|
||||
from src.api.base.context import ApiRequestContext
|
||||
from src.config.constants import CacheTTL
|
||||
from src.database import get_db
|
||||
from src.models.database import StatsDailyError, Usage
|
||||
from src.services.system.time_range import TimeRangeParams
|
||||
from src.utils.cache_decorator import cache_result
|
||||
|
||||
from .common import _apply_admin_default_range, _build_time_range_params, pipeline
|
||||
|
||||
@@ -24,6 +26,18 @@ class AdminErrorDistributionAdapter(AdminApiAdapter):
|
||||
def __init__(self, time_range: TimeRangeParams | None) -> None:
|
||||
self.time_range = _apply_admin_default_range(time_range)
|
||||
|
||||
@cache_result(
|
||||
key_prefix="admin:stats:errors:distribution",
|
||||
ttl=CacheTTL.ADMIN_USAGE_AGGREGATION,
|
||||
user_specific=False,
|
||||
vary_by=[
|
||||
"time_range.start_date",
|
||||
"time_range.end_date",
|
||||
"time_range.preset",
|
||||
"time_range.timezone",
|
||||
"time_range.tz_offset_minutes",
|
||||
],
|
||||
)
|
||||
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
|
||||
if not self.time_range:
|
||||
return {"distribution": [], "trend": []}
|
||||
|
||||
@@ -10,10 +10,12 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from src.api.base.admin_adapter import AdminApiAdapter
|
||||
from src.api.base.context import ApiRequestContext
|
||||
from src.config.constants import CacheTTL
|
||||
from src.database import get_db
|
||||
from src.models.database import StatsDaily
|
||||
from src.services.system.stats_aggregator import StatsAggregatorService
|
||||
from src.services.system.time_range import TimeRangeParams
|
||||
from src.utils.cache_decorator import cache_result
|
||||
|
||||
from .common import _apply_admin_default_range, _build_time_range_params, pipeline
|
||||
|
||||
@@ -24,6 +26,18 @@ class AdminPercentilesAdapter(AdminApiAdapter):
|
||||
def __init__(self, time_range: TimeRangeParams | None) -> None:
|
||||
self.time_range = _apply_admin_default_range(time_range)
|
||||
|
||||
@cache_result(
|
||||
key_prefix="admin:stats:performance:percentiles",
|
||||
ttl=CacheTTL.ADMIN_USAGE_AGGREGATION,
|
||||
user_specific=False,
|
||||
vary_by=[
|
||||
"time_range.start_date",
|
||||
"time_range.end_date",
|
||||
"time_range.preset",
|
||||
"time_range.timezone",
|
||||
"time_range.tz_offset_minutes",
|
||||
],
|
||||
)
|
||||
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
|
||||
if not self.time_range:
|
||||
return []
|
||||
|
||||
@@ -10,9 +10,11 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from src.api.base.admin_adapter import AdminApiAdapter
|
||||
from src.api.base.context import ApiRequestContext
|
||||
from src.config.constants import CacheTTL
|
||||
from src.core.enums import ProviderBillingType
|
||||
from src.database import get_db
|
||||
from src.models.database import Provider
|
||||
from src.utils.cache_decorator import cache_result
|
||||
|
||||
from .common import pipeline
|
||||
|
||||
@@ -20,6 +22,11 @@ router = APIRouter()
|
||||
|
||||
|
||||
class AdminQuotaUsageAdapter(AdminApiAdapter):
|
||||
@cache_result(
|
||||
key_prefix="admin:stats:providers:quota_usage",
|
||||
ttl=CacheTTL.ADMIN_USAGE_AGGREGATION,
|
||||
user_specific=False,
|
||||
)
|
||||
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
|
||||
db = context.db
|
||||
providers = (
|
||||
|
||||
@@ -10,9 +10,11 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from src.api.base.admin_adapter import AdminApiAdapter
|
||||
from src.api.base.context import ApiRequestContext
|
||||
from src.config.constants import CacheTTL
|
||||
from src.database import get_db
|
||||
from src.services.system.stats_aggregator import TimeSeriesFilter, query_time_series
|
||||
from src.services.system.time_range import TimeRangeParams
|
||||
from src.utils.cache_decorator import cache_result
|
||||
|
||||
from .common import _apply_admin_default_range, _build_time_range_params, pipeline
|
||||
|
||||
@@ -32,6 +34,22 @@ class AdminTimeSeriesAdapter(AdminApiAdapter):
|
||||
self.model = model
|
||||
self.provider_name = provider_name
|
||||
|
||||
@cache_result(
|
||||
key_prefix="admin:stats:time_series",
|
||||
ttl=CacheTTL.ADMIN_USAGE_AGGREGATION,
|
||||
user_specific=False,
|
||||
vary_by=[
|
||||
"time_range.start_date",
|
||||
"time_range.end_date",
|
||||
"time_range.preset",
|
||||
"time_range.timezone",
|
||||
"time_range.tz_offset_minutes",
|
||||
"time_range.granularity",
|
||||
"user_id",
|
||||
"model",
|
||||
"provider_name",
|
||||
],
|
||||
)
|
||||
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
|
||||
if not self.time_range:
|
||||
return []
|
||||
|
||||
Reference in New Issue
Block a user