Optimisation des performances
Les performances sont cruciales dans les applications modernes. Cet article explore API Gateway avec Ocelot et les techniques d'optimisation essentielles pour maximiser les performances de vos applications .NET.
Métriques et monitoring
Avant d'optimiser, il faut mesurer :
- Temps de réponse des API
- Utilisation mémoire et CPU
- Throughput et latence
- Allocation d'objets et Garbage Collection
Techniques d'optimisation
1. Mise en cache intelligente
// Configuration du cache mémoire
builder.Services.AddMemoryCache();
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost";
});
// Service avec cache
public class CachedDataService
{
private readonly IMemoryCache _cache;
private readonly IDataRepository _repository;
public CachedDataService(IMemoryCache cache, IDataRepository repository)
{
_cache = cache;
_repository = repository;
}
public async Task<List<DataModel>> GetDataAsync(int categoryId)
{
var cacheKey = $"data_category_{categoryId}";
if (_cache.TryGetValue(cacheKey, out List<DataModel> cachedData))
{
return cachedData;
}
var data = await _repository.GetByCategoryAsync(categoryId);
var cacheOptions = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(15),
SlidingExpiration = TimeSpan.FromMinutes(5),
Priority = CacheItemPriority.High
};
_cache.Set(cacheKey, data, cacheOptions);
return data;
}
}
2. Optimisation des requêtes Entity Framework
// Requêtes optimisées avec projections
public class OptimizedRepository
{
private readonly ApplicationDbContext _context;
public OptimizedRepository(ApplicationDbContext context)
{
_context = context;
}
// Mauvais : charge toute l'entité
public async Task<List<Article>> GetArticlesBad()
{
return await _context.Articles
.Include(a => a.Category)
.Include(a => a.Author)
.ToListAsync();
}
// Bon : projection avec seulement les données nécessaires
public async Task<List<ArticleDto>> GetArticlesOptimized()
{
return await _context.Articles
.Select(a => new ArticleDto
{
Id = a.Id,
Title = a.Title,
Summary = a.Summary,
CategoryName = a.Category.Name,
AuthorName = a.Author.FirstName + " " + a.Author.LastName,
PublishedAt = a.PublishedAt
})
.AsNoTracking() // Pas de tracking pour les lectures seules
.ToListAsync();
}
// Pagination efficace
public async Task<PagedResult<ArticleDto>> GetPagedArticlesAsync(int page, int pageSize)
{
var totalCount = await _context.Articles.CountAsync();
var articles = await _context.Articles
.OrderByDescending(a => a.PublishedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(a => new ArticleDto { /* projection */ })
.AsNoTracking()
.ToListAsync();
return new PagedResult<ArticleDto>(articles, totalCount, page, pageSize);
}
}
3. Optimisation des allocations mémoire
// Utilisation de Span<T> et Memory<T> pour éviter les allocations
public class StringProcessor
{
public string ProcessData(ReadOnlySpan<char> input)
{
// Traitement sans allocation inutile
Span<char> buffer = stackalloc char[256];
var processed = 0;
foreach (var c in input)
{
if (char.IsLetter(c))
{
buffer[processed++] = char.ToUpper(c);
}
}
return new string(buffer[..processed]);
}
// Pool d'objets pour réutiliser les instances
private static readonly ObjectPool<StringBuilder> StringBuilderPool =
new DefaultObjectPoolProvider().CreateStringBuilderPool();
public string BuildString(IEnumerable<string> parts)
{
var sb = StringBuilderPool.Get();
try
{
foreach (var part in parts)
{
sb.Append(part);
sb.Append("|");
}
return sb.ToString();
}
finally
{
StringBuilderPool.Return(sb);
}
}
}
Monitoring avec Application Insights
// Configuration du monitoring
builder.Services.AddApplicationInsightsTelemetry();
// Custom metrics
public class PerformanceMetrics
{
private readonly TelemetryClient _telemetryClient;
public PerformanceMetrics(TelemetryClient telemetryClient)
{
_telemetryClient = telemetryClient;
}
public void TrackProcessingTime(string operation, double duration)
{
_telemetryClient.TrackMetric($"ProcessingTime_{operation}", duration);
}
public void TrackCacheHit(string cacheKey)
{
_telemetryClient.TrackEvent("CacheHit", new Dictionary<string, string>
{
{ "CacheKey", cacheKey }
});
}
}
Benchmarking avec BenchmarkDotNet
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net80)]
public class PerformanceBenchmark
{
private readonly List<string> _data = Enumerable.Range(0, 1000)
.Select(i => $"Item {i}").ToList();
[Benchmark]
public string ConcatenateWithStringBuilder()
{
var sb = new StringBuilder();
foreach (var item in _data)
{
sb.Append(item);
}
return sb.ToString();
}
[Benchmark]
public string ConcatenateWithJoin()
{
return string.Join("", _data);
}
}
Conclusion
L'optimisation des performances avec API Gateway avec Ocelot nécessite une approche méthodique. Mesurez d'abord, optimisez ensuite, et validez les résultats. Les techniques présentées - mise en cache, optimisation des requêtes, gestion mémoire et monitoring - forment la base d'applications performantes et scalables.