574 lines
15 KiB
Markdown
Raw Permalink Normal View History

2026-01-06 22:59:58 +08:00
# Design Document: UploadInspectionTaskResult Performance Optimization
## Overview
本设计文档描述了对 `UploadInspectionTaskResult` 方法的性能优化方案。该方法目前存在超时问题,主要原因包括同步数据库查询、低效的文件处理、缺少批量操作和缓存机制。
优化策略包括:
- 异步数据库查询和并行加载
- 实现 Redis 缓存层
- 批量数据库操作
- 异步文件 I/O 和并行处理
- 内存优化和资源管理
- 性能监控和日志记录
## Architecture
### 当前架构问题
```
Request → Validate → Load All Presets (Sync) → Load All Videos (Sync) →
Loop Items → Process Files (Sync) → Insert MongoDB (One by One) → Response
```
**性能瓶颈:**
1. 同步加载所有预置点和视频设备到内存
2. 循环中逐个处理文件
3. 循环中逐个插入 MongoDB
4. 每次请求都重新加载相同数据
### 优化后架构
```
Request → Validate →
[Parallel: Check Cache → Load Presets (Async) + Load Videos (Async)] →
[Parallel: Process Files Batch] →
Batch Insert MongoDB →
Update Cache → Response
```
**优化点:**
1. 并行异步加载数据
2. Redis 缓存层减少数据库查询
3. 并行处理文件
4. 批量插入 MongoDB
5. 性能监控和指标收集
## Components and Interfaces
### 1. CacheService (新增组件)
负责管理 Redis 缓存操作。
```csharp
public interface ICacheService
{
Task<List<PresetPoint>> GetPresetsAsync(bool forceRefresh = false);
Task<List<VideoDev>> GetVideosAsync(bool forceRefresh = false);
Task InvalidatePresetsAsync();
Task InvalidateVideosAsync();
}
public class CacheService : ICacheService
{
private readonly IRedisRepository<List<PresetPoint>, string> _presetCache;
private readonly IRedisRepository<List<VideoDev>, string> _videoCache;
private readonly IRepository<PresetPoint, Guid> _presetRepository;
private readonly IRepository<VideoDev, Guid> _videoRepository;
private readonly TimeSpan _cacheExpiration = TimeSpan.FromHours(1);
// Implementation details...
}
```
### 2. FileProcessingService (新增组件)
负责并行处理文件上传。
```csharp
public interface IFileProcessingService
{
Task<List<string>> ProcessFilesAsync(
IFormCollection form,
int itemIndex,
int fileCount,
string relativePath,
CancellationToken cancellationToken = default);
}
public class FileProcessingService : IFileProcessingService
{
private const int MaxDegreeOfParallelism = 4;
private const int MaxFileSize = 50 * 1024 * 1024;
private static readonly string[] AllowedExtensions = { ".jpg", ".jpeg", ".png", ".bmp", ".gif" };
// Implementation details...
}
```
### 3. BatchOperationService (新增组件)
负责批量数据库操作。
```csharp
public interface IBatchOperationService
{
Task<bool> BatchInsertInspectionResultsAsync(
List<OriginalInspectionStoreResult> items,
CancellationToken cancellationToken = default);
}
public class BatchOperationService : IBatchOperationService
{
private readonly IMongoDbRepository<BsonDocument, string> _mongo;
private const int BatchSize = 100;
// Implementation details...
}
```
### 4. PerformanceMonitoringService (新增组件)
负责性能监控和指标收集。
```csharp
public interface IPerformanceMonitoringService
{
IDisposable BeginOperation(string operationName);
void RecordMetric(string metricName, long value);
void RecordMetric(string metricName, TimeSpan duration);
}
public class PerformanceMonitoringService : IPerformanceMonitoringService
{
private readonly ILogger _logger;
public IDisposable BeginOperation(string operationName)
{
return new OperationTimer(operationName, this);
}
private class OperationTimer : IDisposable
{
private readonly Stopwatch _stopwatch;
private readonly string _operationName;
private readonly PerformanceMonitoringService _service;
public OperationTimer(string operationName, PerformanceMonitoringService service)
{
_operationName = operationName;
_service = service;
_stopwatch = Stopwatch.StartNew();
}
public void Dispose()
{
_stopwatch.Stop();
_service.RecordMetric(_operationName, _stopwatch.Elapsed);
}
}
}
```
### 5. 优化后的 HandleUploadInspectionItemsAppService
主要修改:
- 注入新的服务组件
- 使用缓存服务获取数据
- 使用文件处理服务并行处理文件
- 使用批量操作服务插入数据
- 添加性能监控
```csharp
public class HandleUploadInspectionItemsAppService : ApplicationService
{
private readonly ICacheService _cacheService;
private readonly IFileProcessingService _fileProcessingService;
private readonly IBatchOperationService _batchOperationService;
private readonly IPerformanceMonitoringService _performanceMonitoring;
// 其他现有依赖...
public async Task<ExcternalResquestSimpleResult> UploadInspectionTaskResult()
{
using var _ = _performanceMonitoring.BeginOperation("UploadInspectionTaskResult");
// 优化后的实现...
}
}
```
## Data Models
### CacheKey 常量
```csharp
public static class CacheKeys
{
public const string PresetPoints = "cache:preset_points";
public const string VideoDevices = "cache:video_devices";
}
```
### PerformanceMetrics 模型
```csharp
public class PerformanceMetrics
{
public string OperationName { get; set; }
public DateTime Timestamp { get; set; }
public TimeSpan Duration { get; set; }
public int ItemsProcessed { get; set; }
public long MemoryUsed { get; set; }
}
```
### 现有模型保持不变
- `OriginalInspectionStoreResult`
- `PresetPoint`
- `VideoDev`
- `ExcternalResquestSimpleResult`
## Correctness Properties
*A property is a characteristic or behavior that should hold true across all valid executions of a system—essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
### Property 1: Parallel Query Execution Performance
*For any* data loading operation requiring multiple data sources, executing queries in parallel should complete faster than executing them sequentially.
**Validates: Requirements 1.3**
### Property 2: Cache Hit Reduces Database Calls
*For any* data request within the cache validity period, subsequent requests for the same data should retrieve from cache without querying the database.
**Validates: Requirements 1.4, 4.1, 4.2**
### Property 3: Cache Miss Triggers Database Load and Update
*For any* data request when cache is empty or expired, the system should load from database and update the cache.
**Validates: Requirements 4.3**
### Property 4: Parallel File Processing Performance
*For any* file collection exceeding the threshold, parallel processing should complete faster than sequential processing.
**Validates: Requirements 2.3**
### Property 5: File Processing Resilience
*For any* collection of files where one or more fail to save, the system should continue processing remaining files and return results for successful saves.
**Validates: Requirements 2.4**
### Property 6: Batch Insert Reduces Database Calls
*For any* collection of inspection results, batch insertion should result in fewer database calls than individual insertions.
**Validates: Requirements 3.1**
### Property 7: Batch Size Partitioning
*For any* data collection exceeding the batch size threshold, the system should partition into multiple batches to avoid memory overflow.
**Validates: Requirements 3.3**
### Property 8: Performance Logging Completeness
*For any* method execution, the system should log both start timestamp and total execution time.
**Validates: Requirements 6.1, 6.2, 6.3**
### Property 9: Exception Context Logging
*For any* exception that occurs during processing, the system should log detailed error context including operation name, timestamp, and error details.
**Validates: Requirements 6.5**
### Property 10: Early Validation Rejection
*For any* request with invalid or missing required fields, the system should return an error immediately without performing subsequent processing operations.
**Validates: Requirements 7.1, 7.2**
### Property 11: Invalid File Skipping
*For any* file collection containing files that violate size or type constraints, the system should skip invalid files while successfully processing valid ones.
**Validates: Requirements 7.3**
### Property 12: Validation Error Clarity
*For any* validation failure, the error message should clearly indicate which validation rule failed and what the expected format is.
**Validates: Requirements 7.4**
### Property 13: Concurrent Request Independence
*For any* set of concurrent requests, processing one request should not block or interfere with the processing of other requests.
**Validates: Requirements 8.1**
### Property 14: Thread-Safe Resource Access
*For any* shared resource accessed by concurrent requests, the system should use appropriate synchronization to prevent race conditions and data corruption.
**Validates: Requirements 8.2**
## Error Handling
### 1. Request Validation Errors
**Strategy:** Fail fast with clear error messages
```csharp
if (request == null || request.ContentType == null)
{
return new ExcternalResquestSimpleResult
{
Status = 1,
Message = "Invalid request: missing content type"
};
}
```
### 2. Cache Failures
**Strategy:** Fallback to database with logging
```csharp
try
{
return await _cacheService.GetPresetsAsync();
}
catch (RedisException ex)
{
Log4Helper.Warn(this.GetType(), "Cache unavailable, falling back to database", ex);
return await _presetRepository.GetAllAsync();
}
```
### 3. File Processing Errors
**Strategy:** Continue processing with error collection
```csharp
var errors = new List<string>();
foreach (var file in files)
{
try
{
await ProcessFileAsync(file);
}
catch (Exception ex)
{
errors.Add($"Failed to process {file.FileName}: {ex.Message}");
Log4Helper.Error(this.GetType(), $"File processing error", ex);
}
}
```
### 4. Database Operation Errors
**Strategy:** Retry with exponential backoff for transient errors
```csharp
var retryPolicy = Policy
.Handle<MongoException>()
.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
await retryPolicy.ExecuteAsync(async () =>
{
await _batchOperationService.BatchInsertInspectionResultsAsync(items);
});
```
### 5. Timeout Handling
**Strategy:** Use CancellationToken with configurable timeout
```csharp
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
try
{
await ProcessRequestAsync(request, cts.Token);
}
catch (OperationCanceledException)
{
Log4Helper.Warn(this.GetType(), "Operation timed out");
return new ExcternalResquestSimpleResult
{
Status = 1,
Message = "Request processing timed out"
};
}
```
## Testing Strategy
### Dual Testing Approach
本项目将采用单元测试和性能测试相结合的方式:
**单元测试:**
- 验证特定的实现细节(如异步方法签名)
- 测试边界条件和错误处理
- 验证配置选项的行为
- 使用 xUnit 测试框架
**性能测试:**
- 验证并行处理比串行处理更快
- 验证缓存减少数据库调用
- 验证批量操作减少往返次数
- 测试并发处理能力
- 使用 BenchmarkDotNet 进行性能基准测试
### 单元测试示例
```csharp
[Fact]
public async Task GetPresetsAsync_ShouldReturnFromCache_WhenCacheHit()
{
// Arrange
var cachedData = new List<PresetPoint> { /* test data */ };
_mockRedisRepository.Setup(x => x.GetOneAsync(It.IsAny<string>()))
.ReturnsAsync(cachedData);
// Act
var result = await _cacheService.GetPresetsAsync();
// Assert
Assert.Equal(cachedData, result);
_mockPresetRepository.Verify(x => x.GetAllAsync(), Times.Never);
}
[Fact]
public async Task ProcessFilesAsync_ShouldSkipInvalidFiles_AndProcessValidOnes()
{
// Arrange
var form = CreateFormWithMixedFiles(); // Some valid, some invalid
// Act
var result = await _fileProcessingService.ProcessFilesAsync(form, 0, 5, "test/");
// Assert
Assert.True(result.Count > 0); // Valid files processed
Assert.True(result.Count < 5); // Invalid files skipped
}
```
### 性能测试示例
```csharp
[Benchmark]
public async Task ParallelQueryPerformance()
{
var tasks = new[]
{
_cacheService.GetPresetsAsync(),
_cacheService.GetVideosAsync()
};
await Task.WhenAll(tasks);
}
[Benchmark]
public async Task SequentialQueryPerformance()
{
await _cacheService.GetPresetsAsync();
await _cacheService.GetVideosAsync();
}
[Fact]
public async Task BatchInsert_ShouldBeFasterThan_IndividualInserts()
{
// Arrange
var items = GenerateTestItems(100);
// Act
var batchTime = await MeasureExecutionTime(() =>
_batchOperationService.BatchInsertInspectionResultsAsync(items));
var individualTime = await MeasureExecutionTime(() =>
InsertIndividually(items));
// Assert
Assert.True(batchTime < individualTime);
}
```
### 集成测试
```csharp
[Fact]
public async Task UploadInspectionTaskResult_EndToEnd_Performance()
{
// Arrange
var request = CreateTestRequest(itemCount: 50, filesPerItem: 3);
// Act
var stopwatch = Stopwatch.StartNew();
var result = await _appService.UploadInspectionTaskResult();
stopwatch.Stop();
// Assert
Assert.Equal(0, result.Status);
Assert.True(stopwatch.ElapsedMilliseconds < 5000); // Should complete in < 5 seconds
}
```
### 测试配置
- 最小迭代次数100 次(对于性能基准测试)
- 每个正确性属性必须有对应的测试
- 测试标签格式:**Feature: upload-inspection-task-result-performance, Property {number}: {property_text}**
### 测试覆盖率目标
- 代码覆盖率:> 80%
- 分支覆盖率:> 70%
- 性能回归检测:所有关键路径必须有性能基准
## Implementation Notes
### 优化优先级
1. **高优先级(立即实施):**
- 实现 Redis 缓存层
- 异步数据库查询
- 批量 MongoDB 插入
2. **中优先级(第二阶段):**
- 并行文件处理
- 性能监控和日志
- 错误处理优化
3. **低优先级(可选):**
- 流量控制和队列机制
- 高级性能指标收集
### 配置参数
```csharp
public class PerformanceOptimizationConfig
{
public bool EnableCache { get; set; } = true;
public TimeSpan CacheExpiration { get; set; } = TimeSpan.FromHours(1);
public int MaxDegreeOfParallelism { get; set; } = 4;
public int BatchSize { get; set; } = 100;
public int MaxConcurrentRequests { get; set; } = 50;
public TimeSpan RequestTimeout { get; set; } = TimeSpan.FromSeconds(30);
}
```
### 性能指标
需要监控的关键指标:
- 请求处理总时间
- 数据库查询时间
- 缓存命中率
- 文件处理时间
- 批量插入时间
- 内存使用量
- 并发请求数
### 向后兼容性
- 所有优化应该保持 API 接口不变
- 缓存功能可通过配置禁用以保持原有行为
- 性能监控不应影响功能正确性