Files
Masterarbeit/Versuche/Versuch 02/Ergenisse/software/Performance_Patterns.md

1177 lines
38 KiB
Markdown

# Performance Patterns and Optimization - Centron Enterprise Application
## Overview
This document provides comprehensive coverage of performance patterns and optimizations within the Centron .NET 8 enterprise application. Performance patterns encompass caching strategies, query optimization, lazy loading, asynchronous programming, resource management, and monitoring techniques that ensure optimal application performance under various load conditions.
## Performance Architecture
### **Performance Design Principles**
The Centron application implements performance optimization through multiple strategies:
1. **Caching**: Multi-level caching from database to UI components
2. **Asynchronous Processing**: Non-blocking I/O operations and background tasks
3. **Query Optimization**: Efficient database queries with proper indexing
4. **Resource Management**: Proper disposal and pooling of expensive resources
5. **Lazy Loading**: On-demand data loading to reduce initial load times
6. **Background Processing**: Offload heavy operations to background services
## Caching Patterns
### **1. Multi-Level Caching Strategy**
```csharp
public interface ICacheService
{
Task<Result<T>> GetAsync<T>(string key) where T : class;
Task<Result> SetAsync<T>(string key, T value, TimeSpan? expiry = null) where T : class;
Task<Result> RemoveAsync(string key);
Task<Result> RemoveByPatternAsync(string pattern);
Task<Result> ClearAsync();
}
public class HybridCacheService : ICacheService
{
private readonly IMemoryCache _memoryCache;
private readonly IDistributedCache _distributedCache;
private readonly ILogger<HybridCacheService> _logger;
private readonly CacheConfiguration _config;
public HybridCacheService(
IMemoryCache memoryCache,
IDistributedCache distributedCache,
CacheConfiguration config)
{
_memoryCache = memoryCache;
_distributedCache = distributedCache;
_config = config;
}
public async Task<Result<T>> GetAsync<T>(string key) where T : class
{
try
{
// Level 1: Memory Cache (fastest)
if (_memoryCache.TryGetValue(key, out T cachedValue))
{
return Result<T>.AsSuccess(cachedValue);
}
// Level 2: Distributed Cache (Redis/SQL Server)
var distributedValue = await _distributedCache.GetStringAsync(key);
if (!string.IsNullOrEmpty(distributedValue))
{
var deserializedValue = JsonSerializer.Deserialize<T>(distributedValue);
// Store in memory cache for faster subsequent access
var memoryExpiry = TimeSpan.FromMinutes(_config.MemoryCacheMinutes);
_memoryCache.Set(key, deserializedValue, memoryExpiry);
return Result<T>.AsSuccess(deserializedValue);
}
return Result<T>.AsError("Cache miss");
}
catch (Exception ex)
{
_logger.LogError(ex, "Cache get operation failed for key: {Key}", key);
return Result<T>.AsError($"Cache operation failed: {ex.Message}");
}
}
public async Task<Result> SetAsync<T>(string key, T value, TimeSpan? expiry = null) where T : class
{
try
{
var cacheExpiry = expiry ?? TimeSpan.FromMinutes(_config.DefaultExpiryMinutes);
// Set in memory cache
_memoryCache.Set(key, value, TimeSpan.FromMinutes(_config.MemoryCacheMinutes));
// Set in distributed cache
var serializedValue = JsonSerializer.Serialize(value);
var options = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = cacheExpiry
};
await _distributedCache.SetStringAsync(key, serializedValue, options);
return Result.AsSuccess();
}
catch (Exception ex)
{
_logger.LogError(ex, "Cache set operation failed for key: {Key}", key);
return Result.AsError($"Cache operation failed: {ex.Message}");
}
}
}
// Cache-Aside Pattern Implementation
public class CachedAccountRepository : IAccountRepository
{
private readonly IAccountRepository _baseRepository;
private readonly ICacheService _cacheService;
private readonly TimeSpan _cacheExpiry = TimeSpan.FromMinutes(15);
public CachedAccountRepository(IAccountRepository baseRepository, ICacheService cacheService)
{
_baseRepository = baseRepository;
_cacheService = cacheService;
}
public async Task<Result<Account>> GetAccountByIdAsync(int accountId)
{
var cacheKey = $"account:{accountId}";
// Try cache first
var cachedResult = await _cacheService.GetAsync<Account>(cacheKey);
if (cachedResult.Status == ResultStatus.Success)
return cachedResult;
// Cache miss - get from database
var dbResult = await _baseRepository.GetAccountByIdAsync(accountId);
if (dbResult.Status == ResultStatus.Success)
{
// Cache the result
await _cacheService.SetAsync(cacheKey, dbResult.Data, _cacheExpiry);
}
return dbResult;
}
public async Task<Result<Account>> SaveAccountAsync(Account account)
{
var result = await _baseRepository.SaveAccountAsync(account);
if (result.Status == ResultStatus.Success)
{
// Update cache with new data
var cacheKey = $"account:{account.I3D}";
await _cacheService.SetAsync(cacheKey, result.Data, _cacheExpiry);
// Invalidate related cached data
await InvalidateRelatedCacheAsync(account);
}
return result;
}
private async Task InvalidateRelatedCacheAsync(Account account)
{
// Remove account lists that might include this account
await _cacheService.RemoveByPatternAsync("accounts:list:*");
await _cacheService.RemoveByPatternAsync($"customer:{account.CustomerNumber}:*");
}
}
```
### **2. Application-Level Caching Patterns**
```csharp
public class CachedTableService
{
private readonly ICacheService _cacheService;
private readonly ICachedTableRepository _repository;
public async Task<Result<List<T>>> GetCachedDataAsync<T>(string tableName) where T : class
{
var cacheKey = $"cached_table:{tableName}";
var cachedResult = await _cacheService.GetAsync<CachedTableData<T>>(cacheKey);
if (cachedResult.Status == ResultStatus.Success)
{
var cachedData = cachedResult.Data;
// Check if cache needs refresh
var lastModified = await _repository.GetLastModifiedAsync(tableName);
if (lastModified <= cachedData.CacheTime)
{
return Result<List<T>>.AsSuccess(cachedData.Data);
}
}
// Cache is stale or missing - refresh from database
return await RefreshCachedDataAsync<T>(tableName);
}
private async Task<Result<List<T>>> RefreshCachedDataAsync<T>(string tableName) where T : class
{
var dataResult = await _repository.GetDataAsync<T>(tableName);
if (dataResult.Status != ResultStatus.Success)
return dataResult;
var cachedData = new CachedTableData<T>
{
Data = dataResult.Data,
CacheTime = DateTime.UtcNow,
TableName = tableName
};
// Cache with appropriate expiry based on table type
var expiry = GetCacheExpiryForTable(tableName);
await _cacheService.SetAsync($"cached_table:{tableName}", cachedData, expiry);
return Result<List<T>>.AsSuccess(cachedData.Data);
}
private TimeSpan GetCacheExpiryForTable(string tableName)
{
return tableName.ToLower() switch
{
"countries" => TimeSpan.FromHours(24), // Static reference data
"currencies" => TimeSpan.FromHours(12),
"products" => TimeSpan.FromMinutes(30), // More dynamic data
"prices" => TimeSpan.FromMinutes(5), // Frequently changing
_ => TimeSpan.FromHours(1) // Default
};
}
}
// Background cache warming service
public class CacheWarmupService : BackgroundService
{
private readonly ICachedTableService _cachedTableService;
private readonly ILogger<CacheWarmupService> _logger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
await WarmupCriticalCaches();
await Task.Delay(TimeSpan.FromMinutes(30), stoppingToken);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during cache warmup");
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
}
private async Task WarmupCriticalCaches()
{
var criticalTables = new[] { "countries", "currencies", "taxrates", "paymentmethods" };
var warmupTasks = criticalTables.Select(async table =>
{
try
{
await _cachedTableService.GetCachedDataAsync<object>(table);
_logger.LogDebug("Warmed up cache for table: {Table}", table);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to warm up cache for table: {Table}", table);
}
});
await Task.WhenAll(warmupTasks);
}
}
```
## Asynchronous Programming Patterns
### **1. Async/Await Best Practices**
```csharp
public class OptimizedBusinessLogic : BaseBL
{
// Correct async pattern with ConfigureAwait
public async Task<Result<List<Account>>> GetAccountsAsync(AccountFilter filter)
{
try
{
// Use ConfigureAwait(false) in library code to avoid deadlocks
var accounts = await _accountRepository
.GetAccountsAsync(filter)
.ConfigureAwait(false);
return Result<List<Account>>.AsSuccess(accounts);
}
catch (Exception ex)
{
return Result<List<Account>>.AsError($"Failed to get accounts: {ex.Message}");
}
}
// Parallel processing for independent operations
public async Task<Result<AccountSummary>> GetAccountSummaryAsync(int accountId)
{
try
{
// Start all async operations concurrently
var accountTask = _accountRepository.GetAccountByIdAsync(accountId);
var contractsTask = _contractRepository.GetContractsByAccountAsync(accountId);
var invoicesTask = _invoiceRepository.GetRecentInvoicesAsync(accountId);
var paymentsTask = _paymentRepository.GetRecentPaymentsAsync(accountId);
// Wait for all to complete
await Task.WhenAll(accountTask, contractsTask, invoicesTask, paymentsTask)
.ConfigureAwait(false);
// Process results
var account = await accountTask.ConfigureAwait(false);
var contracts = await contractsTask.ConfigureAwait(false);
var invoices = await invoicesTask.ConfigureAwait(false);
var payments = await paymentsTask.ConfigureAwait(false);
if (account.Status != ResultStatus.Success)
return Result<AccountSummary>.FromResult(account);
var summary = new AccountSummary
{
Account = account.Data,
ActiveContracts = contracts.Data?.Where(c => c.IsActive).ToList() ?? new List<Contract>(),
RecentInvoices = invoices.Data ?? new List<Invoice>(),
RecentPayments = payments.Data ?? new List<Payment>(),
TotalOutstanding = CalculateOutstanding(invoices.Data, payments.Data)
};
return Result<AccountSummary>.AsSuccess(summary);
}
catch (Exception ex)
{
return Result<AccountSummary>.AsError($"Failed to get account summary: {ex.Message}");
}
}
// Batch processing with controlled concurrency
public async Task<Result<BatchProcessResult>> ProcessAccountsBatchAsync(List<int> accountIds, int maxConcurrency = 10)
{
var semaphore = new SemaphoreSlim(maxConcurrency, maxConcurrency);
var results = new ConcurrentBag<ProcessResult>();
var errors = new ConcurrentBag<string>();
var tasks = accountIds.Select(async accountId =>
{
await semaphore.WaitAsync().ConfigureAwait(false);
try
{
var result = await ProcessSingleAccountAsync(accountId).ConfigureAwait(false);
results.Add(result);
}
catch (Exception ex)
{
errors.Add($"Account {accountId}: {ex.Message}");
}
finally
{
semaphore.Release();
}
});
await Task.WhenAll(tasks).ConfigureAwait(false);
return Result<BatchProcessResult>.AsSuccess(new BatchProcessResult
{
SuccessfulResults = results.ToList(),
Errors = errors.ToList(),
TotalProcessed = accountIds.Count
});
}
}
```
### **2. Background Task Processing Patterns**
```csharp
public abstract class ManagedBackgroundService : BackgroundService
{
private readonly ILogger _logger;
private readonly IServiceProvider _serviceProvider;
protected abstract string ServiceName { get; }
protected abstract TimeSpan GetExecutionInterval();
protected abstract void ExecuteService(CancellationToken stoppingToken);
protected virtual void InitializeService(CancellationToken stoppingToken) { }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Starting background service: {ServiceName}", ServiceName);
try
{
InitializeService(stoppingToken);
while (!stoppingToken.IsCancellationRequested)
{
var stopwatch = Stopwatch.StartNew();
try
{
using (var scope = _serviceProvider.CreateScope())
{
ExecuteService(stoppingToken);
}
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error executing background service: {ServiceName}", ServiceName);
}
stopwatch.Stop();
var executionTime = stopwatch.Elapsed;
var interval = GetExecutionInterval();
// Ensure we don't execute too frequently
var delay = interval - executionTime;
if (delay > TimeSpan.Zero)
{
await Task.Delay(delay, stoppingToken);
}
}
}
finally
{
_logger.LogInformation("Background service stopped: {ServiceName}", ServiceName);
}
}
}
// Specialized cache update service
public class CacheUpdateService : ManagedBackgroundService
{
private DateTime? _lastOptimizedUpdate = null;
protected override string ServiceName => "CacheUpdateService";
protected override void ExecuteService(CancellationToken stoppingToken)
{
using (var session = new BLSession())
{
// Regular cache updates
session.GetBL<CachedTableBL>().ExecuteCacheUpdates().ThrowIfError();
// Optimized cache updates (less frequent)
if (_lastOptimizedUpdate == null || DateTime.Now - _lastOptimizedUpdate > TimeSpan.FromMinutes(1))
{
session.GetBL<CachedTableBL>().ExecuteOptimizedCacheUpdates().ThrowIfError();
_lastOptimizedUpdate = DateTime.Now;
}
}
}
protected override TimeSpan GetExecutionInterval()
{
return TimeSpan.FromSeconds(2); // High frequency for immediate updates
}
protected override void InitializeService(CancellationToken stoppingToken)
{
try
{
using var session = new BLSession();
session.GetBL<CachedTableBL>().RepairIfNecessaryCacheStatistic();
}
catch (Exception e)
{
_logger.LogError(e, "Failed to repair the cached statistic table");
}
}
}
// Task queue background processor
public class TaskQueueProcessor : BackgroundService
{
private readonly ITaskQueue _taskQueue;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<TaskQueueProcessor> _logger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
var task = await _taskQueue.DequeueAsync(stoppingToken);
// Process task in separate scope for proper DI cleanup
using (var scope = _serviceProvider.CreateScope())
{
await ProcessTaskAsync(task, scope.ServiceProvider);
}
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing background task");
}
}
}
private async Task ProcessTaskAsync(BackgroundTask task, IServiceProvider serviceProvider)
{
try
{
task.Status = TaskStatus.Processing;
task.StartedAt = DateTime.UtcNow;
var processor = serviceProvider.GetRequiredService(task.ProcessorType) as ITaskProcessor;
var result = await processor.ProcessAsync(task.Data);
task.Status = result.Status == ResultStatus.Success ? TaskStatus.Completed : TaskStatus.Failed;
task.Result = result.Data;
task.Error = result.Error;
}
catch (Exception ex)
{
task.Status = TaskStatus.Failed;
task.Error = ex.Message;
_logger.LogError(ex, "Task processing failed: {TaskId}", task.Id);
}
finally
{
task.CompletedAt = DateTime.UtcNow;
await _taskQueue.UpdateTaskAsync(task);
}
}
}
```
## Database Query Optimization Patterns
### **1. Efficient Query Patterns**
```csharp
public class OptimizedQueryRepository
{
private readonly ISession _session;
// Projection query - only select needed fields
public async Task<Result<List<AccountSummaryDto>>> GetAccountSummariesAsync(AccountFilter filter)
{
try
{
var query = _session.QueryOver<Account>()
.Where(a => a.IsActive)
.SelectList(list => list
.Select(a => a.I3D).WithAlias(() => alias.Id)
.Select(a => a.CompanyName).WithAlias(() => alias.CompanyName)
.Select(a => a.CustomerNumber).WithAlias(() => alias.CustomerNumber)
.Select(a => a.CreditLimit).WithAlias(() => alias.CreditLimit))
.TransformUsing(Transformers.AliasToBean<AccountSummaryDto>());
// Apply filters
if (filter.CustomerNumbers?.Any() == true)
query.WhereRestrictionOn(a => a.CustomerNumber).IsIn(filter.CustomerNumbers);
if (!string.IsNullOrEmpty(filter.CompanyNameFilter))
query.Where(Restrictions.InsensitiveLike("CompanyName", filter.CompanyNameFilter, MatchMode.Anywhere));
// Pagination
if (filter.PageSize > 0)
{
query.Skip(filter.Page * filter.PageSize).Take(filter.PageSize);
}
var results = await Task.FromResult(query.List<AccountSummaryDto>());
return Result<List<AccountSummaryDto>>.AsSuccess(results.ToList());
}
catch (Exception ex)
{
return Result<List<AccountSummaryDto>>.AsError($"Query failed: {ex.Message}");
}
}
// Batch loading with proper joins
public async Task<Result<List<Account>>> GetAccountsWithContactsAsync(List<int> accountIds)
{
try
{
// Use batch loading to avoid N+1 queries
var accounts = await Task.FromResult(
_session.QueryOver<Account>()
.Fetch(a => a.Contacts).Eager
.Fetch(a => a.Addresses).Eager
.WhereRestrictionOn(a => a.I3D).IsIn(accountIds)
.List());
return Result<List<Account>>.AsSuccess(accounts.ToList());
}
catch (Exception ex)
{
return Result<List<Account>>.AsError($"Batch query failed: {ex.Message}");
}
}
// Optimized aggregation query
public async Task<Result<AccountStatistics>> GetAccountStatisticsAsync()
{
try
{
var statistics = await Task.FromResult(
_session.QueryOver<Account>()
.SelectList(list => list
.SelectCount(a => a.I3D).WithAlias(() => alias.TotalAccounts)
.SelectSum(a => a.CreditLimit).WithAlias(() => alias.TotalCreditLimit)
.SelectAvg(a => a.CreditLimit).WithAlias(() => alias.AverageCreditLimit))
.Where(a => a.IsActive)
.TransformUsing(Transformers.AliasToBean<AccountStatistics>())
.SingleOrDefault<AccountStatistics>());
return Result<AccountStatistics>.AsSuccess(statistics);
}
catch (Exception ex)
{
return Result<AccountStatistics>.AsError($"Statistics query failed: {ex.Message}");
}
}
}
// Lazy loading pattern for large datasets
public class LazyLoadingService
{
public async Task<Result<PagedResult<T>>> GetPagedDataAsync<T>(
IQueryable<T> baseQuery,
int page,
int pageSize,
CancellationToken cancellationToken = default)
{
try
{
var totalCount = await baseQuery.CountAsync(cancellationToken);
var items = await baseQuery
.Skip(page * pageSize)
.Take(pageSize)
.ToListAsync(cancellationToken);
return Result<PagedResult<T>>.AsSuccess(new PagedResult<T>
{
Items = items,
TotalCount = totalCount,
Page = page,
PageSize = pageSize,
TotalPages = (int)Math.Ceiling((double)totalCount / pageSize)
});
}
catch (Exception ex)
{
return Result<PagedResult<T>>.AsError($"Paged query failed: {ex.Message}");
}
}
}
```
### **2. Connection and Transaction Optimization**
```csharp
public class OptimizedDataAccess
{
// Connection pooling and session management
public class OptimizedSession : IDisposable
{
private readonly ISession _session;
private readonly ITransaction _transaction;
public OptimizedSession()
{
_session = SessionFactory.OpenSession();
_transaction = _session.BeginTransaction(IsolationLevel.ReadCommitted);
}
public async Task<Result<T>> ExecuteAsync<T>(Func<ISession, Task<T>> operation)
{
try
{
var result = await operation(_session);
await _transaction.CommitAsync();
return Result<T>.AsSuccess(result);
}
catch (Exception ex)
{
await _transaction.RollbackAsync();
return Result<T>.AsError($"Operation failed: {ex.Message}");
}
}
public void Dispose()
{
_transaction?.Dispose();
_session?.Dispose();
}
}
// Batch operations for better performance
public async Task<Result> BatchInsertAsync<T>(IEnumerable<T> entities) where T : class
{
try
{
using var session = new OptimizedSession();
return await session.ExecuteAsync(async s =>
{
var batchSize = 100;
var batch = 0;
foreach (var entity in entities)
{
await s.SaveAsync(entity);
if (++batch % batchSize == 0)
{
await s.FlushAsync();
s.Clear();
}
}
await s.FlushAsync();
return "Success";
});
}
catch (Exception ex)
{
return Result.AsError($"Batch insert failed: {ex.Message}");
}
}
}
```
## Resource Management and Pooling Patterns
### **1. Object Pooling Pattern**
```csharp
public class ObjectPool<T> : IDisposable where T : class, IDisposable, new()
{
private readonly ConcurrentQueue<T> _objects = new();
private readonly Func<T> _objectFactory;
private readonly Action<T> _resetAction;
private readonly int _maxSize;
private int _currentCount = 0;
public ObjectPool(Func<T> objectFactory = null, Action<T> resetAction = null, int maxSize = 100)
{
_objectFactory = objectFactory ?? (() => new T());
_resetAction = resetAction;
_maxSize = maxSize;
}
public PooledObject<T> Get()
{
if (_objects.TryDequeue(out T item))
{
Interlocked.Decrement(ref _currentCount);
return new PooledObject<T>(item, this);
}
return new PooledObject<T>(_objectFactory(), this);
}
internal void Return(T item)
{
if (_currentCount < _maxSize)
{
_resetAction?.Invoke(item);
_objects.Enqueue(item);
Interlocked.Increment(ref _currentCount);
}
else
{
item.Dispose();
}
}
public void Dispose()
{
while (_objects.TryDequeue(out T item))
{
item.Dispose();
}
}
}
public struct PooledObject<T> : IDisposable where T : class, IDisposable
{
private readonly ObjectPool<T> _pool;
public T Object { get; }
internal PooledObject(T obj, ObjectPool<T> pool)
{
Object = obj;
_pool = pool;
}
public void Dispose()
{
_pool.Return(Object);
}
}
// Usage example - HTTP client pooling
public class HttpClientPool
{
private static readonly ObjectPool<HttpClient> _httpClientPool =
new ObjectPool<HttpClient>(
() => new HttpClient(),
client => { /* Reset client state */ },
50);
public static async Task<Result<string>> MakeRequestAsync(string url)
{
using var pooledClient = _httpClientPool.Get();
var client = pooledClient.Object;
try
{
var response = await client.GetStringAsync(url);
return Result<string>.AsSuccess(response);
}
catch (Exception ex)
{
return Result<string>.AsError($"Request failed: {ex.Message}");
}
}
}
```
### **2. Memory Management Patterns**
```csharp
public class MemoryOptimizedService
{
private readonly MemoryPool<byte> _memoryPool = MemoryPool<byte>.Shared;
public async Task<Result<ProcessedData>> ProcessLargeDataAsync(Stream dataStream)
{
using var owner = _memoryPool.Rent(8192); // Rent 8KB buffer
var buffer = owner.Memory;
try
{
var processedData = new ProcessedData();
int bytesRead;
while ((bytesRead = await dataStream.ReadAsync(buffer)) > 0)
{
// Process data in chunks to avoid large allocations
var chunk = buffer.Slice(0, bytesRead);
ProcessChunk(chunk.Span, processedData);
}
return Result<ProcessedData>.AsSuccess(processedData);
}
catch (Exception ex)
{
return Result<ProcessedData>.AsError($"Processing failed: {ex.Message}");
}
// Buffer is automatically returned to pool when owner is disposed
}
private void ProcessChunk(Span<byte> chunk, ProcessedData result)
{
// Process data without additional allocations
for (int i = 0; i < chunk.Length; i++)
{
result.Checksum += chunk[i];
}
result.BytesProcessed += chunk.Length;
}
}
// Large object heap optimization
public class LargeDataProcessor
{
public Result<ProcessedResult> ProcessLargeDataset(IEnumerable<LargeDataItem> items)
{
try
{
var result = new ProcessedResult();
// Process in batches to avoid LOH pressure
const int batchSize = 1000;
var batch = new List<LargeDataItem>(batchSize);
foreach (var item in items)
{
batch.Add(item);
if (batch.Count == batchSize)
{
ProcessBatch(batch, result);
batch.Clear(); // Clear instead of creating new list
}
}
// Process remaining items
if (batch.Count > 0)
{
ProcessBatch(batch, result);
}
return Result<ProcessedResult>.AsSuccess(result);
}
catch (Exception ex)
{
return Result<ProcessedResult>.AsError($"Large data processing failed: {ex.Message}");
}
}
private void ProcessBatch(List<LargeDataItem> batch, ProcessedResult result)
{
// Process batch and update result
foreach (var item in batch)
{
result.ProcessedCount++;
result.TotalValue += item.Value;
}
// Force garbage collection after processing batch
// Only do this for large batches to avoid performance impact
if (batch.Count >= 1000)
{
GC.Collect(0, GCCollectionMode.Optimized);
}
}
}
```
## Performance Monitoring and Profiling Patterns
### **1. Performance Metrics Collection**
```csharp
public class PerformanceMonitoringService
{
private readonly IMetricsCollector _metricsCollector;
private readonly ILogger<PerformanceMonitoringService> _logger;
public async Task<Result<T>> MonitorOperationAsync<T>(
string operationName,
Func<Task<Result<T>>> operation,
Dictionary<string, object> additionalMetrics = null)
{
var stopwatch = Stopwatch.StartNew();
var startTime = DateTime.UtcNow;
try
{
var result = await operation();
stopwatch.Stop();
var duration = stopwatch.Elapsed;
// Collect performance metrics
var metrics = new OperationMetrics
{
OperationName = operationName,
Duration = duration,
Success = result.Status == ResultStatus.Success,
StartTime = startTime,
EndTime = DateTime.UtcNow,
AdditionalMetrics = additionalMetrics ?? new Dictionary<string, object>()
};
await _metricsCollector.RecordOperationAsync(metrics);
// Log slow operations
if (duration > TimeSpan.FromSeconds(5))
{
_logger.LogWarning("Slow operation detected: {Operation} took {Duration}ms",
operationName, duration.TotalMilliseconds);
}
return result;
}
catch (Exception ex)
{
stopwatch.Stop();
await _metricsCollector.RecordOperationAsync(new OperationMetrics
{
OperationName = operationName,
Duration = stopwatch.Elapsed,
Success = false,
StartTime = startTime,
EndTime = DateTime.UtcNow,
Error = ex.Message
});
return Result<T>.AsError($"Operation {operationName} failed: {ex.Message}");
}
}
public async Task<Result<PerformanceReport>> GeneratePerformanceReportAsync(TimeSpan period)
{
try
{
var endTime = DateTime.UtcNow;
var startTime = endTime - period;
var metrics = await _metricsCollector.GetMetricsAsync(startTime, endTime);
var report = new PerformanceReport
{
Period = period,
TotalOperations = metrics.Count,
SuccessfulOperations = metrics.Count(m => m.Success),
AverageDuration = TimeSpan.FromMilliseconds(metrics.Average(m => m.Duration.TotalMilliseconds)),
SlowestOperations = metrics.OrderByDescending(m => m.Duration).Take(10).ToList(),
MostFrequentOperations = metrics.GroupBy(m => m.OperationName)
.OrderByDescending(g => g.Count())
.Take(10)
.ToDictionary(g => g.Key, g => g.Count())
};
return Result<PerformanceReport>.AsSuccess(report);
}
catch (Exception ex)
{
return Result<PerformanceReport>.AsError($"Failed to generate performance report: {ex.Message}");
}
}
}
// Performance measurement attribute
[AttributeUsage(AttributeTargets.Method)]
public class MeasurePerformanceAttribute : Attribute
{
public string OperationName { get; set; }
public bool LogSlowOperations { get; set; } = true;
public int SlowThresholdMs { get; set; } = 1000;
}
// Performance interceptor
public class PerformanceInterceptor
{
private readonly PerformanceMonitoringService _performanceService;
public async Task<object> InterceptAsync(MethodInvocationContext context)
{
var perfAttribute = context.Method.GetCustomAttribute<MeasurePerformanceAttribute>();
if (perfAttribute == null)
return await context.ProceedAsync();
var operationName = perfAttribute.OperationName ?? $"{context.TargetType.Name}.{context.Method.Name}";
return await _performanceService.MonitorOperationAsync(operationName, async () =>
{
var result = await context.ProceedAsync();
return Result<object>.AsSuccess(result);
});
}
}
```
### **2. Resource Usage Monitoring**
```csharp
public class ResourceMonitoringService : BackgroundService
{
private readonly ILogger<ResourceMonitoringService> _logger;
private readonly IMetricsCollector _metricsCollector;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
await CollectResourceMetricsAsync();
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error collecting resource metrics");
}
}
}
private async Task CollectResourceMetricsAsync()
{
using var process = Process.GetCurrentProcess();
var metrics = new ResourceMetrics
{
Timestamp = DateTime.UtcNow,
WorkingSet = process.WorkingSet64,
PrivateMemory = process.PrivateMemorySize64,
VirtualMemory = process.VirtualMemorySize64,
ProcessorTime = process.TotalProcessorTime,
ThreadCount = process.Threads.Count,
HandleCount = process.HandleCount
};
// Add GC metrics
for (int generation = 0; generation <= GC.MaxGeneration; generation++)
{
metrics.GcCollections[generation] = GC.CollectionCount(generation);
}
metrics.TotalMemory = GC.GetTotalMemory(false);
await _metricsCollector.RecordResourceMetricsAsync(metrics);
// Alert on high resource usage
if (metrics.WorkingSet > 1_000_000_000) // > 1GB
{
_logger.LogWarning("High memory usage detected: {WorkingSet}MB",
metrics.WorkingSet / 1_000_000);
}
if (metrics.ThreadCount > 100)
{
_logger.LogWarning("High thread count detected: {ThreadCount}",
metrics.ThreadCount);
}
}
}
```
## Performance Best Practices and Guidelines
### **1. General Performance Guidelines**
- **Measure First**: Always measure performance before optimizing
- **Profile Regularly**: Use profiling tools to identify bottlenecks
- **Cache Strategically**: Cache expensive operations but avoid over-caching
- **Optimize Database Access**: Use proper indexing and query optimization
- **Manage Resources**: Properly dispose of resources and use pooling
### **2. Async Programming Guidelines**
- **Use ConfigureAwait(false)**: In library code to avoid deadlocks
- **Avoid Blocking**: Never use .Result or .Wait() on async methods
- **Parallel Processing**: Use Task.WhenAll for independent operations
- **Limit Concurrency**: Use SemaphoreSlim to control concurrent operations
### **3. Memory Management Guidelines**
- **Avoid Large Object Heap**: Keep objects under 85KB when possible
- **Use Object Pooling**: For frequently created/disposed objects
- **Stream Processing**: Process large datasets in chunks
- **Monitor GC Pressure**: Track garbage collection frequency and duration
### **4. Caching Guidelines**
- **Cache Expiry**: Set appropriate expiry times based on data volatility
- **Cache Invalidation**: Implement proper cache invalidation strategies
- **Cache Levels**: Use multi-level caching for optimal performance
- **Cache Monitoring**: Monitor cache hit rates and effectiveness
## Conclusion
The Centron application implements comprehensive performance patterns that ensure:
- **Scalability**: Multi-level caching and efficient resource management
- **Responsiveness**: Asynchronous programming and background processing
- **Efficiency**: Query optimization and memory management
- **Monitoring**: Performance metrics collection and resource monitoring
- **Maintainability**: Clear patterns and best practices for performance optimization
These performance patterns provide the foundation for a high-performance enterprise application that can scale with growing user demands while maintaining optimal response times and resource utilization.