574 lines
15 KiB
Markdown
574 lines
15 KiB
Markdown
# 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 接口不变
|
||
- 缓存功能可通过配置禁用以保持原有行为
|
||
- 性能监控不应影响功能正确性
|