mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +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:
@@ -6,6 +6,7 @@ import ipaddress
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -159,7 +160,12 @@ class ManagementTokenService:
|
||||
ValueError: 如果名称已存在或超过数量限制
|
||||
"""
|
||||
# 检查用户 Token 数量限制
|
||||
token_count = db.query(ManagementToken).filter(ManagementToken.user_id == user_id).count()
|
||||
token_count = int(
|
||||
db.query(func.count(ManagementToken.id))
|
||||
.filter(ManagementToken.user_id == user_id)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
max_tokens = config.management_token_max_per_user
|
||||
if token_count >= max_tokens:
|
||||
raise ValueError(f"已达到 Token 数量上限({max_tokens})")
|
||||
@@ -245,7 +251,7 @@ class ManagementTokenService:
|
||||
if is_active is not None:
|
||||
query = query.filter(ManagementToken.is_active == is_active)
|
||||
|
||||
total = query.count()
|
||||
total = int(query.with_entities(func.count(ManagementToken.id)).scalar() or 0)
|
||||
tokens = query.order_by(ManagementToken.created_at.desc()).offset(skip).limit(limit).all()
|
||||
|
||||
return tokens, total
|
||||
|
||||
@@ -6,7 +6,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy import and_, func
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -255,14 +255,15 @@ class ModelService:
|
||||
|
||||
# 检查这是否是该 GlobalModel 的最后一个关联提供商
|
||||
if model.global_model_id:
|
||||
other_implementations = (
|
||||
db.query(Model)
|
||||
other_implementations = int(
|
||||
db.query(func.count(Model.id))
|
||||
.filter(
|
||||
Model.global_model_id == model.global_model_id,
|
||||
Model.id != model_id,
|
||||
Model.is_active == True,
|
||||
)
|
||||
.count()
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
|
||||
if other_implementations == 0:
|
||||
|
||||
@@ -14,7 +14,7 @@ from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import httpx
|
||||
from sqlalchemy import update
|
||||
from sqlalchemy import func, update
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from src.core.exceptions import InvalidRequestException, NotFoundException
|
||||
@@ -452,7 +452,7 @@ class ProxyNodeService:
|
||||
raise InvalidRequestException(f"status 必须是以下之一: {sorted(allowed)}", "status")
|
||||
query = query.filter(ProxyNode.status == ProxyNodeStatus(normalized))
|
||||
|
||||
total = query.count()
|
||||
total = int(query.with_entities(func.count(ProxyNode.id)).scalar() or 0)
|
||||
nodes = query.order_by(ProxyNode.name.asc()).offset(skip).limit(limit).all()
|
||||
return nodes, total
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy import func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from src.core.exceptions import ForbiddenException, NotFoundException
|
||||
@@ -87,7 +87,7 @@ class AnnouncementService:
|
||||
)
|
||||
|
||||
# 分页
|
||||
total = query.count()
|
||||
total = int(query.with_entities(func.count(Announcement.id)).scalar() or 0)
|
||||
announcements = query.offset(offset).limit(limit).all()
|
||||
|
||||
# 获取已读状态
|
||||
|
||||
@@ -338,8 +338,8 @@ class AuditService:
|
||||
)
|
||||
|
||||
# 获取最近的可疑活动
|
||||
recent_suspicious = (
|
||||
db.query(AuditLog)
|
||||
recent_suspicious = int(
|
||||
db.query(func.count(AuditLog.id))
|
||||
.filter(
|
||||
AuditLog.user_id == user_id,
|
||||
AuditLog.event_type.in_(
|
||||
@@ -350,7 +350,8 @@ class AuditService:
|
||||
),
|
||||
AuditLog.created_at >= cutoff_time,
|
||||
)
|
||||
.count()
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
|
||||
return {
|
||||
|
||||
@@ -116,8 +116,8 @@ class SystemConfigService:
|
||||
"description": "详细日志保留天数,超过此天数后压缩 request_body 和 response_body 到压缩字段",
|
||||
},
|
||||
"compressed_log_retention_days": {
|
||||
"value": 90,
|
||||
"description": "压缩日志保留天数,超过此天数后删除压缩的 body 字段(保留headers和统计)",
|
||||
"value": 30,
|
||||
"description": "压缩记录保留天数,超过此天数后删除压缩的 body 字段(保留headers和统计)",
|
||||
},
|
||||
"header_retention_days": {
|
||||
"value": 90,
|
||||
@@ -125,7 +125,7 @@ class SystemConfigService:
|
||||
},
|
||||
"log_retention_days": {
|
||||
"value": 365,
|
||||
"description": "完整日志保留天数,超过此天数后删除整条记录(保留核心统计)",
|
||||
"description": "请求记录保存天数,超过此天数后删除整条使用记录",
|
||||
},
|
||||
"enable_auto_cleanup": {
|
||||
"value": True,
|
||||
|
||||
@@ -1052,7 +1052,7 @@ class MaintenanceScheduler:
|
||||
# 获取配置参数
|
||||
detail_retention = SystemConfigService.get_config(db, "detail_log_retention_days", 7)
|
||||
compressed_retention = SystemConfigService.get_config(
|
||||
db, "compressed_log_retention_days", 90
|
||||
db, "compressed_log_retention_days", 30
|
||||
)
|
||||
header_retention = SystemConfigService.get_config(db, "header_retention_days", 90)
|
||||
log_retention = SystemConfigService.get_config(db, "log_retention_days", 365)
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from src.core.api_format.metadata import can_passthrough_endpoint
|
||||
@@ -104,13 +105,14 @@ class UsageActiveRequestsMixin:
|
||||
"""
|
||||
cutoff_time = datetime.now(timezone.utc) - timedelta(minutes=timeout_minutes)
|
||||
|
||||
return (
|
||||
db.query(Usage)
|
||||
return int(
|
||||
db.query(func.count(Usage.id))
|
||||
.filter(
|
||||
Usage.status.in_(["pending", "streaming"]),
|
||||
Usage.created_at < cutoff_time,
|
||||
)
|
||||
.count()
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy import case, func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from src.core.logger import logger
|
||||
@@ -239,6 +239,21 @@ class UsageQueryMixin:
|
||||
func.sum(Usage.total_tokens).label("total_tokens"),
|
||||
func.sum(Usage.total_cost_usd).label("total_cost_usd"),
|
||||
func.avg(Usage.response_time_ms).label("avg_response_time"),
|
||||
func.sum(
|
||||
case(
|
||||
(
|
||||
(Usage.status_code == 200) & Usage.response_time_ms.isnot(None),
|
||||
Usage.response_time_ms,
|
||||
),
|
||||
else_=0,
|
||||
)
|
||||
).label("success_response_time_sum"),
|
||||
func.sum(
|
||||
case(
|
||||
((Usage.status_code == 200) & Usage.response_time_ms.isnot(None), 1),
|
||||
else_=0,
|
||||
)
|
||||
).label("success_response_time_count"),
|
||||
)
|
||||
|
||||
# 过滤掉 pending/streaming 状态的请求(与上方明细查询一致)
|
||||
@@ -268,6 +283,8 @@ class UsageQueryMixin:
|
||||
"avg_response_time_ms": (
|
||||
float(row.avg_response_time) if row.avg_response_time else 0
|
||||
),
|
||||
"success_response_time_sum_ms": float(row.success_response_time_sum or 0.0),
|
||||
"success_response_time_count": int(row.success_response_time_count or 0),
|
||||
}
|
||||
for row in summary
|
||||
]
|
||||
|
||||
@@ -266,7 +266,9 @@ class UserService:
|
||||
db.query(AnnouncementRead).filter(AnnouncementRead.user_id == user_id).delete(
|
||||
synchronize_session=False
|
||||
)
|
||||
api_key_count = db.query(ApiKey).filter(ApiKey.user_id == user_id).count()
|
||||
api_key_count = int(
|
||||
db.query(func.count(ApiKey.id)).filter(ApiKey.user_id == user_id).scalar() or 0
|
||||
)
|
||||
db.query(ApiKey).filter(ApiKey.user_id == user_id).delete(synchronize_session=False)
|
||||
|
||||
# 现在删除用户(Usage, AuditLog, RequestAttempt 会通过数据库 SET NULL 保留)
|
||||
|
||||
Reference in New Issue
Block a user