save progress
This commit is contained in:
@@ -107,15 +107,18 @@ public sealed class ResolutionCacheService : IResolutionCacheService
|
||||
private readonly ResolutionCacheOptions _options;
|
||||
private readonly ILogger<ResolutionCacheService> _logger;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
private readonly IRandomSource _random;
|
||||
|
||||
public ResolutionCacheService(
|
||||
IConnectionMultiplexer redis,
|
||||
IOptions<ResolutionCacheOptions> options,
|
||||
ILogger<ResolutionCacheService> logger)
|
||||
ILogger<ResolutionCacheService> logger,
|
||||
IRandomSource random)
|
||||
{
|
||||
_redis = redis ?? throw new ArgumentNullException(nameof(redis));
|
||||
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_random = random ?? throw new ArgumentNullException(nameof(random));
|
||||
_jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
@@ -129,7 +132,7 @@ public sealed class ResolutionCacheService : IResolutionCacheService
|
||||
try
|
||||
{
|
||||
var db = _redis.GetDatabase();
|
||||
var value = await db.StringGetAsync(cacheKey);
|
||||
var value = await db.StringGetAsync(cacheKey).WaitAsync(ct).ConfigureAwait(false);
|
||||
|
||||
if (value.IsNullOrEmpty)
|
||||
{
|
||||
@@ -142,7 +145,7 @@ public sealed class ResolutionCacheService : IResolutionCacheService
|
||||
// Check for probabilistic early expiry
|
||||
if (_options.EnableEarlyExpiry && cached is not null)
|
||||
{
|
||||
var ttl = await db.KeyTimeToLiveAsync(cacheKey);
|
||||
var ttl = await db.KeyTimeToLiveAsync(cacheKey).WaitAsync(ct).ConfigureAwait(false);
|
||||
if (ShouldExpireEarly(ttl))
|
||||
{
|
||||
_logger.LogDebug("Early expiry triggered for key {CacheKey}", cacheKey);
|
||||
@@ -153,6 +156,10 @@ public sealed class ResolutionCacheService : IResolutionCacheService
|
||||
_logger.LogDebug("Cache hit for key {CacheKey}", cacheKey);
|
||||
return cached;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to get cache entry for key {CacheKey}", cacheKey);
|
||||
@@ -168,9 +175,13 @@ public sealed class ResolutionCacheService : IResolutionCacheService
|
||||
var db = _redis.GetDatabase();
|
||||
var value = JsonSerializer.Serialize(result, _jsonOptions);
|
||||
|
||||
await db.StringSetAsync(cacheKey, value, ttl);
|
||||
await db.StringSetAsync(cacheKey, value, ttl).WaitAsync(ct).ConfigureAwait(false);
|
||||
_logger.LogDebug("Cached resolution for key {CacheKey} with TTL {Ttl}", cacheKey, ttl);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to cache resolution for key {CacheKey}", cacheKey);
|
||||
@@ -182,17 +193,55 @@ public sealed class ResolutionCacheService : IResolutionCacheService
|
||||
{
|
||||
try
|
||||
{
|
||||
var server = _redis.GetServer(_redis.GetEndPoints().First());
|
||||
var db = _redis.GetDatabase();
|
||||
|
||||
var keys = server.Keys(pattern: pattern).ToArray();
|
||||
|
||||
if (keys.Length > 0)
|
||||
var endpoints = _redis.GetEndPoints();
|
||||
if (endpoints.Length == 0)
|
||||
{
|
||||
await db.KeyDeleteAsync(keys);
|
||||
_logger.LogInformation("Invalidated {Count} cache entries matching pattern {Pattern}",
|
||||
keys.Length, pattern);
|
||||
_logger.LogWarning("No Redis endpoints available for pattern invalidation");
|
||||
return;
|
||||
}
|
||||
|
||||
const int batchSize = 500;
|
||||
long totalDeleted = 0;
|
||||
|
||||
foreach (var endpoint in endpoints)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var server = _redis.GetServer(endpoint);
|
||||
if (!server.IsConnected)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var buffer = new List<RedisKey>(batchSize);
|
||||
foreach (var key in server.Keys(pattern: pattern, pageSize: batchSize))
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
buffer.Add(key);
|
||||
if (buffer.Count >= batchSize)
|
||||
{
|
||||
totalDeleted += await db.KeyDeleteAsync(buffer.ToArray()).WaitAsync(ct).ConfigureAwait(false);
|
||||
buffer.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.Count > 0)
|
||||
{
|
||||
totalDeleted += await db.KeyDeleteAsync(buffer.ToArray()).WaitAsync(ct).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (totalDeleted > 0)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Invalidated {Count} cache entries matching pattern {Pattern}",
|
||||
totalDeleted,
|
||||
pattern);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -271,7 +320,7 @@ public sealed class ResolutionCacheService : IResolutionCacheService
|
||||
return true;
|
||||
|
||||
// Probabilistic early expiry using exponential decay
|
||||
var random = Random.Shared.NextDouble();
|
||||
var random = _random.NextDouble();
|
||||
var threshold = _options.EarlyExpiryFactor * Math.Exp(-remainingTtl.Value.TotalSeconds / 3600);
|
||||
|
||||
return random < threshold;
|
||||
|
||||
Reference in New Issue
Block a user