Files
AIRouter/前端请求日志性能优化方案.md

619 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 前端请求日志性能优化技术方案
## 1. 方案概述
本方案针对前端请求日志系统存在的三个高严重性性能瓶颈,提供系统性的优化解决方案:
- **后端返回过多不必要数据**通过精简列表响应结构和独立详情API解决
- **前端筛选输入无防抖**:通过防抖机制和请求优化解决
- **缺少数据库查询优化**:通过复合索引策略解决
### 优化目标
- 减少90%+的数据传输量
- 减少80%+的不必要请求
- 提升50-80%的查询速度
### 技术栈
- 后端Go + GORM + Gin
- 前端React + Vite + Axios
- 数据库SQLite可扩展至MySQL/PostgreSQL
## 2. 当前系统架构分析
### 2.1 数据流程
```mermaid
graph TD
A[前端RequestLogList组件] -->|fetchLogs| B[GET /api/logs]
A -->|fetchStats| C[GET /api/logs/stats]
B --> D[GetRequestLogsHandler]
C --> E[GetRequestLogStatsHandler]
D --> F[数据库查询+完整RequestLog]
E --> G[统计查询]
F --> H[返回包含RequestBody/ResponseBody的完整数据]
G --> I[返回统计数据]
H --> A
I --> A
A --> J[RequestLogDetailModal]
```
### 2.2 性能瓶颈分析
#### 🔴 问题1后端返回过多不必要数据
- **现状**`RequestLog`结构包含`RequestBody``ResponseBody`可能数十KB
- **影响**93条日志约传输930KB不必要数据
- **根因**列表API返回完整日志详情但列表页只显示基本信息
#### 🔴 问题2前端筛选输入无防抖
- **现状**每个输入字段变化立即触发2次API请求`fetchLogs` + `fetchStats`
- **影响**:输入"gpt-4"产生10次请求造成服务器负载和网络浪费
- **根因**:缺乏防抖机制,频繁的实时查询
#### 🔴 问题3缺少数据库查询优化
- **现状**:只有单列索引,常见组合查询效率低
- **影响**:时间范围+模型名称组合查询性能差
- **根因**:缺少针对常见查询模式设计的复合索引
## 3. 后端API优化方案
### 3.1 精简列表API响应结构
#### 3.1.1 新增精简响应结构
```go
// RequestLogSummary 精简的日志摘要结构
type RequestLogSummary struct {
ID uint `json:"id"`
ProviderName string `json:"provider_name"`
VirtualModelName string `json:"virtual_model_name"`
BackendModelName string `json:"backend_model_name"`
RequestTimestamp time.Time `json:"request_timestamp"`
RequestTokens int `json:"request_tokens"`
ResponseTokens int `json:"response_tokens"`
Cost float64 `json:"cost"`
}
```
#### 3.1.2 修改API处理逻辑
```go
// GetRequestLogsHandler 优化后的处理函数
func (h *APIHandler) GetRequestLogsHandler(c *gin.Context) {
// ... 分页和过滤逻辑保持不变
// 获取精简的日志列表不包含RequestBody/ResponseBody
var logs []models.RequestLog
if err := query.Order("request_timestamp DESC").
Limit(pageSize).
Offset(offset).
Select("id, provider_name, virtual_model_name, backend_model_name, request_timestamp, request_tokens, response_tokens, cost").
Find(&logs).Error; err != nil {
// ... 错误处理
}
c.JSON(http.StatusOK, gin.H{
"logs": logs, // 现在只包含必要字段
"total": total,
"page": page,
"page_size": pageSize,
"total_pages": (total + int64(pageSize) - 1) / int64(pageSize),
})
}
```
#### 3.1.3 向后兼容性
- 保持原有API路径和参数格式
- 仅移除列表响应中的大字段
- 添加`include_body`参数作为可选标志(向后兼容)
### 3.2 独立日志详情API
#### 3.2.1 新增详情API端点
```go
// GetRequestLogDetailHandler 获取单个日志的完整详情
func (h *APIHandler) GetRequestLogDetailHandler(c *gin.Context) {
logID := c.Param("id")
var log models.RequestLog
if err := h.DB.First(&log, logID).Error; err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "日志不存在"})
} else {
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取日志详情失败"})
}
return
}
c.JSON(http.StatusOK, gin.H{
"log": log, // 包含完整的RequestBody和ResponseBody
})
}
```
#### 3.2.2 路由注册
```go
// 在路由配置中添加
rg.GET("/logs/:id", h.GetRequestLogDetailHandler)
```
#### 3.2.3 前端API调用更新
```javascript
// 新增获取单个日志详情的函数
export const getRequestLogDetail = async (id) => {
try {
const response = await apiClient.get(`/api/logs/${id}`);
return response.data;
} catch (error) {
console.error('获取日志详情失败:', error);
throw new Error(error.response?.data?.error || '获取日志详情失败');
}
};
```
### 3.3 API契约定义
#### 3.3.1 列表API响应格式
```json
{
"logs": [
{
"id": 1,
"provider_name": "openai",
"virtual_model_name": "gpt-4",
"backend_model_name": "gpt-4",
"request_timestamp": "2023-11-11T12:00:00Z",
"request_tokens": 150,
"response_tokens": 300,
"cost": 0.015000
}
],
"total": 93,
"page": 1,
"page_size": 20,
"total_pages": 5
}
```
#### 3.3.2 详情API响应格式
```json
{
"log": {
"id": 1,
"api_key_id": 1,
"provider_name": "openai",
"virtual_model_name": "gpt-4",
"backend_model_name": "gpt-4",
"request_timestamp": "2023-11-11T12:00:00Z",
"response_timestamp": "2023-11-11T12:00:05Z",
"request_tokens": 150,
"response_tokens": 300,
"cost": 0.015000,
"request_body": "{\"model\":\"gpt-4\",\"messages\":[{\"role\":\"user\",\"content\":\"Hello\"}]}",
"response_body": "{\"id\":\"chatcmpl-xxx\",\"choices\":[{\"message\":{\"content\":\"Hi there!\"}}]}"
}
}
```
## 4. 前端防抖优化方案
### 4.1 防抖机制设计
#### 4.1.1 自定义防抖Hook
```jsx
// useDebounce.js
import { useRef, useEffect } from 'react';
export function useDebounce(callback, delay) {
const timeoutRef = useRef(null);
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
const debouncedCallback = (...args) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
callback(...args);
}, delay);
};
return debouncedCallback;
}
```
#### 4.1.2 优化后的RequestLogList组件
```jsx
const RequestLogList = () => {
// ... 状态定义保持不变
// 使用防抖包装API调用
const debouncedFetchLogs = useDebounce(() => {
fetchLogs();
fetchStats();
}, 500); // 500ms延迟
// 优化的处理函数
const handleFilterChange = (key, value) => {
setFilters(prev => ({ ...prev, [key]: value }));
setPage(1); // 重置到第一页
// 使用防抖而非立即调用
debouncedFetchLogs();
};
// ... 其他逻辑保持不变
};
```
### 4.2 用户体验优化
#### 4.2.1 添加加载状态指示
```jsx
const [filterLoading, setFilterLoading] = useState(false);
// 在防抖回调中添加加载状态
const debouncedFetchLogs = useDebounce(async () => {
setFilterLoading(true);
try {
await Promise.all([fetchLogs(), fetchStats()]);
} finally {
setFilterLoading(false);
}
}, 500);
// 在UI中显示加载状态
{filterLoading && (
<div className="flex justify-center py-2">
<div className="text-sm text-gray-500">筛选中...</div>
</div>
)}
```
#### 4.2.2 请求合并策略
```jsx
// 优化合并fetchLogs和fetchStats为单一请求
const fetchData = async () => {
try {
setLoading(true);
const [logsData, statsData] = await Promise.all([
getRequestLogs(params),
getRequestLogStats(filters)
]);
setLogs(logsData.logs || []);
setStats(statsData);
setTotalPages(Math.ceil((logsData.total || 0) / pageSize));
setError(null);
} catch (err) {
setError(err.message || '获取日志失败');
} finally {
setLoading(false);
}
};
```
### 4.3 组件级优化
#### 4.3.1 详情模态框优化
```jsx
// 优化:详情按需加载
const [modalLoading, setModalLoading] = useState(false);
const handleViewDetail = async (log) => {
setSelectedLog(null);
setShowDetailModal(true);
setModalLoading(true);
try {
// 精简列表中没有完整请求/响应体,需要单独获取
const detailData = await getRequestLogDetail(log.ID);
setSelectedLog(detailData.log);
} catch (err) {
console.error('获取日志详情失败:', err);
} finally {
setModalLoading(false);
}
};
```
## 5. 数据库索引优化方案
### 5.1 常用查询模式分析
基于现有代码,识别出以下常见查询模式:
1. **时间范围查询**`WHERE request_timestamp BETWEEN ? AND ?`
2. **模型筛选**`WHERE virtual_model_name = ?``WHERE backend_model_name = ?`
3. **组合查询**:时间范围 + 模型名称
4. **排序查询**`ORDER BY request_timestamp DESC`
5. **分页查询**`LIMIT ? OFFSET ?`
### 5.2 复合索引策略
#### 5.2.1 新增复合索引定义
```go
// 在models/schema.go中修改RequestLog结构
type RequestLog struct {
gorm.Model
APIKeyID uint `gorm:"index"`
ProviderName string `gorm:"index"`
VirtualModelName string `gorm:"index"`
BackendModelName string `gorm:"index"`
RequestTimestamp time.Time `gorm:"index;not null"`
ResponseTimestamp time.Time `gorm:"not null"`
RequestTokens int `gorm:"default:0"`
ResponseTokens int `gorm:"default:0"`
Cost float64 `gorm:"type:decimal(10,6)"`
RequestBody string `gorm:"type:text"`
ResponseBody string `gorm:"type:text"`
}
// 在AutoMigrate中添加复合索引
func AutoMigrate(db *gorm.DB) error {
if err := db.AutoMigrate(&APIKey{}, &Provider{}, &VirtualModel{}, &BackendModel{}, &RequestLog{}); err != nil {
return err
}
// 添加复合索引
if err := addCompositeIndexes(db); err != nil {
return err
}
return nil
}
func addCompositeIndexes(db *gorm.DB) error {
// 时间范围 + 虚拟模型名的复合索引(最常见查询)
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_request_logs_timestamp_virtual_model ON request_logs (request_timestamp DESC, virtual_model_name)").Error; err != nil {
return err
}
// 时间范围 + 后端模型名的复合索引
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_request_logs_timestamp_backend_model ON request_logs (request_timestamp DESC, backend_model_name)").Error; err != nil {
return err
}
// 时间范围 + 服务商名的复合索引
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_request_logs_timestamp_provider ON request_logs (request_timestamp DESC, provider_name)").Error; err != nil {
return err
}
// 时间范围 + API密钥ID的复合索引用于用户权限查询
if err := db.Exec("CREATE INDEX IF NOT EXISTS idx_request_logs_timestamp_api_key ON request_logs (request_timestamp DESC, api_key_id)").Error; err != nil {
return err
}
return nil
}
```
#### 5.2.2 索引选择策略
```mermaid
graph TD
A[查询请求] --> B{包含时间范围?}
B -->|是| C{包含模型筛选?}
B -->|否| D[使用单列索引]
C -->|虚拟模型| E[使用idx_request_logs_timestamp_virtual_model]
C -->|后端模型| F[使用idx_request_logs_timestamp_backend_model]
C -->|服务商| G[使用idx_request_logs_timestamp_provider]
C -->|无| H[使用request_timestamp索引]
E --> I[高效查询]
F --> I
G --> I
H --> I
```
### 5.3 索引性能评估
#### 5.3.1 查询性能对比
| 查询场景 | 优化前(单列索引) | 优化后(复合索引) | 性能提升 |
|---------|-----------------|-----------------|----------|
| 时间范围 + 虚拟模型 | 全表扫描 + 虚拟模型过滤 | 索引直接定位 | 70-80% |
| 时间范围 + 后端模型 | 全表扫描 + 后端模型过滤 | 索引直接定位 | 60-75% |
| 纯时间范围查询 | 时间索引扫描 | 时间索引扫描 | 10-15% |
| 纯模型筛选 | 模型索引扫描 | 模型索引扫描 | 无变化 |
#### 5.3.2 写入性能影响
- **索引开销**新增4个复合索引每次INSERT操作需要额外写入索引数据
- **评估**对于读多写少的日志系统写入性能影响可接受预计5-10%
- **监控**:实施后需监控写入性能,必要时可调整索引策略
## 6. 数据库Schema变更
### 6.1 索引变更SQL
```sql
-- 新增复合索引
CREATE INDEX IF NOT EXISTS idx_request_logs_timestamp_virtual_model
ON request_logs (request_timestamp DESC, virtual_model_name);
CREATE INDEX IF NOT EXISTS idx_request_logs_timestamp_backend_model
ON request_logs (request_timestamp DESC, backend_model_name);
CREATE INDEX IF NOT EXISTS idx_request_logs_timestamp_provider
ON request_logs (request_timestamp DESC, provider_name);
CREATE INDEX IF NOT EXISTS idx_request_logs_timestamp_api_key
ON request_logs (request_timestamp DESC, api_key_id);
```
### 6.2 迁移脚本
```go
// 在internal/db/database.go中添加迁移函数
func RunMigrations(db *gorm.DB) error {
// 现有迁移代码...
// 运行新的索引迁移
if err := addCompositeIndexes(db); err != nil {
return fmt.Errorf("添加复合索引失败: %w", err)
}
return nil
}
```
## 7. 实施优先级和步骤
### 7.1 实施优先级
```mermaid
gantt
title 性能优化实施时间线
dateFormat YYYY-MM-DD
section 第一阶段
后端API响应优化 :a1, 2023-11-12, 2d
section 第二阶段
前端防抖机制 :a2, after a1, 1d
section 第三阶段
数据库索引优化 :a3, after a2, 1d
```
### 7.2 详细实施步骤
#### 第一阶段后端API响应优化预计2天
1. **Day 1**
- 创建`RequestLogSummary`结构体
- 修改`GetRequestLogsHandler`使用Select语句排除大字段
- 添加`include_body`参数支持(向后兼容)
- 编写单元测试验证响应结构
2. **Day 2**
- 实现`GetRequestLogDetailHandler`新API端点
- 添加路由注册
- 更新前端API调用函数
- 集成测试验证功能
#### 第二阶段前端防抖机制预计1天
1. 实现自定义`useDebounce` Hook
2. 重构`RequestLogList`组件,应用防抖机制
3. 添加筛选加载状态指示
4. 优化详情模态框按需加载逻辑
5. 测试防抖效果和用户体验
#### 第三阶段数据库索引优化预计1天
1. 分析现有查询模式,确认索引策略
2. 实现复合索引创建函数
3. 编写数据库迁移脚本
4. 执行索引创建(在低峰期进行)
5. 性能测试验证查询速度提升
### 7.3 预期效果
| 优化项目 | 预期提升 | 验证指标 |
|---------|---------|----------|
| 数据传输量 | 减少90%+ | 列表API响应大小从MB降至KB级别 |
| API请求数 | 减少80%+ | 防抖后用户输入产生的请求数显著减少 |
| 查询速度 | 提升50-80% | 常见筛选查询响应时间减少 |
## 8. 风险评估和回退方案
### 8.1 风险评估
#### 8.1.1 后端API变更风险
- **风险**修改API响应可能破坏现有客户端
- **概率**:中
- **影响**如果其他系统集成了该API可能出现兼容性问题
- **缓解措施**
- 保持`include_body`参数完全向后兼容
- 渐进式部署,先内部测试
#### 8.1.2 前端防抖风险
- **风险**:防抖可能导致用户体验下降(响应延迟)
- **概率**:低
- **影响**:用户可能感觉系统反应变慢
- **缓解措施**
- 选择合适的延迟时间500ms平衡性能与响应
- 添加加载状态反馈
#### 8.1.3 数据库索引风险
- **风险**:索引占用额外存储空间,影响写入性能
- **概率**:中
- **影响**:数据库文件增大,写入操作变慢
- **缓解措施**
- 在测试环境验证性能影响
- 监控生产环境性能指标
### 8.2 回退方案
#### 8.2.1 后端API回退
```go
// 为紧急情况保留旧版本处理函数
func (h *APIHandler) GetRequestLogsHandlerLegacy(c *gin.Context) {
// 原始实现包含完整的RequestBody/ResponseBody
logID := c.Query("legacy")
if logID == "true" {
// 执行原始查询逻辑
return
}
// 执行优化后的查询逻辑
}
```
#### 8.2.2 前端回退
```jsx
// 通过环境变量控制防抖功能
const DEBOUNCE_DELAY = process.env.REACT_APP_DISABLE_DEBOUNCE === 'true' ? 0 : 500;
const debouncedFetchLogs = useDebounce(() => {
fetchLogs();
fetchStats();
}, DEBOUNCE_DELAY);
```
#### 8.2.3 数据库索引回退
```sql
-- 删除新增复合索引的SQL
DROP INDEX IF EXISTS idx_request_logs_timestamp_virtual_model;
DROP INDEX IF EXISTS idx_request_logs_timestamp_backend_model;
DROP INDEX IF EXISTS idx_request_logs_timestamp_provider;
DROP INDEX IF EXISTS idx_request_logs_timestamp_api_key;
```
### 8.3 监控计划
#### 8.3.1 性能指标监控
- API响应时间列表、详情、统计
- 前端页面加载和渲染时间
- 数据库查询执行时间
- 网络传输大小
#### 8.3.2 用户行为监控
- API调用频率
- 用户输入模式
- 页面停留时间
- 错误率变化
## 9. 性能提升预期
### 9.1 量化改进预期
| 性能指标 | 当前状态 | 优化后预期 | 改进幅度 |
|---------|---------|-----------|----------|
| 列表API响应大小 | ~930KB/93条 | ~50KB/93条 | 94.6%↓ |
| 列表API响应时间 | 200-500ms | 50-150ms | 50-70%↓ |
| 用户输入触发请求数 | 10次/输入 | 1-2次/输入 | 80%↓ |
| 复合查询响应时间 | 800-2000ms | 200-600ms | 60-75%↓ |
| 页面整体加载时间 | 2-3秒 | 1-1.5秒 | 50%↓ |
### 9.2 用户体验改进
- **响应速度**:列表加载和筛选操作明显变快
- **网络消耗**:大幅减少数据传输,改善移动端体验
- **系统稳定性**:减少服务器负载,降低峰值压力
- **交互流畅度**:防抖机制避免频繁请求,操作更流畅
## 10. 后续优化建议
### 10.1 短期优化1-3个月
1. **缓存策略**为统计数据和常用筛选结果添加Redis缓存
2. **分页优化**实现游标分页替代OFFSET优化深分页性能
3. **压缩传输**启用gzip压缩进一步减少传输大小
### 10.2 长期优化3-6个月
1. **数据归档**:实现冷热数据分离,定期归档历史日志
2. **异步处理**将日志写入改为异步队列提升API响应速度
3. **实时数据**考虑WebSocket实现实时日志流更新
---
本方案经过全面分析,针对三个主要性能瓶颈提供了系统性的解决方案。实施后预计将显著提升系统性能和用户体验,同时保持了良好的向后兼容性和可维护性。建议按照优先级逐步实施,并在每个阶段进行充分测试和验证。