feat(core, infrastructure): RedisCacheService - изменение/добавление полей в кэше списокм. UnitCacheService - оптимизация при получении данных.

This commit is contained in:
Mikhail Trubnikov
2026-06-03 12:09:47 +10:00
parent d1de63e939
commit 66d1fe7b0d
4 changed files with 87 additions and 9 deletions

View File

@@ -14,6 +14,12 @@ namespace PARR.Infrastructure.Redis
/// </summary>
private const int compressionTreshold = 10 * 1024; // 10Kb
/// <summary>
/// Размер порции для Redis Hash.
/// Делим на порции по 5000, чтобы не перегружать буфер команд Redis.
/// </summary>
public const int RedisHashBatchSize = 5000;
private readonly IDistributedCache cache;
private readonly IConnectionMultiplexer connectionMultiplexer;
private readonly ILogger<RedisCacheService> logger;
@@ -212,6 +218,58 @@ namespace PARR.Infrastructure.Redis
await SetHashTtlAsync(hashKey, ttl.Value);
}
public async Task SetHashFieldsAsync<T>(List<(string HashKey, string FieldKey, T Value)> items, TimeSpan? ttl = null, bool useCompression = false)
{
if (items == null || items.Count == 0)
return;
// Множество для отслеживания уникальных HashKey в рамках всей операции,
// чтобы обновить для них TTL всего один раз в самом конце.
var uniqueHashKeys = new HashSet<string>();
// 1. Делим входящие данные на порции по 5000 (RedisHashBatchSize)
foreach (var chunk in items.Chunk(RedisHashBatchSize))
{
var tasks = new List<Task<bool>>(chunk.Length);
// 2. Формируем пайплайн для текущей порции
foreach (var item in chunk)
{
uniqueHashKeys.Add(item.HashKey);
if (useCompression)
{
var byteData = SerializeWithCompression(item.Value);
tasks.Add(redis.HashSetAsync(item.HashKey, item.FieldKey, byteData));
}
else
{
var jsonData = JsonSerializer.Serialize(item.Value);
tasks.Add(redis.HashSetAsync(item.HashKey, item.FieldKey, jsonData));
}
}
logger.LogDebug("Сформировал {Count} одновременных запросов на запись в кэш", tasks.Count);
// 3. Отправляем пачку в Redis и ждем завершения
await Task.WhenAll(tasks);
}
// 4. Если указан TTL, обновляем его для всех затронутых хешей
if (ttl.HasValue)
{
var ttlTasks = new List<Task>(uniqueHashKeys.Count);
foreach (var hashKey in uniqueHashKeys)
{
ttlTasks.Add(SetHashTtlAsync(hashKey, ttl.Value));
}
await Task.WhenAll(ttlTasks);
}
logger.LogDebug("Успешно сохранил {Count} элементов в кэш.", items.Count);
}
public async Task<T?> GetHashFieldAsync<T>(string hashKey, string field, bool useCompression = false)
{
// получить значение поля из Hash
@@ -245,7 +303,7 @@ namespace PARR.Infrastructure.Redis
var allItems = new List<T>(keys.Count);
// Делим на порции по 5000 (оптимально)
foreach (var chunk in keys.Chunk(5000))
foreach (var chunk in keys.Chunk(RedisHashBatchSize))
{
// Формируем пайплайн ТОЛЬКО для 5 000 элементов
var tasks = new List<Task<RedisValue>>(chunk.Length);
@@ -383,7 +441,7 @@ namespace PARR.Infrastructure.Redis
int deletedCount = 0;
// Делим на порции по 5000, чтобы не перегружать буфер команд Redis
foreach (var chunk in keys.Chunk(5000))
foreach (var chunk in keys.Chunk(RedisHashBatchSize))
{
var tasks = new List<Task<bool>>(chunk.Length);
foreach (var key in chunk)
@@ -483,6 +541,5 @@ namespace PARR.Infrastructure.Redis
return JsonSerializer.Deserialize<T>(json);
}
}
}