// Licensed to StellaOps under the BUSL-1.1 license. using Microsoft.Extensions.Logging; using StackExchange.Redis; using System.Collections.Generic; namespace StellaOps.ReachGraph.Cache; public sealed partial class ReachGraphValkeyCache { private const int DefaultScanPageSize = 200; /// public async Task InvalidateAsync( string digest, CancellationToken cancellationToken = default) { ArgumentException.ThrowIfNullOrEmpty(digest); cancellationToken.ThrowIfCancellationRequested(); var db = _redis.GetDatabase(_options.Database); var graphKey = BuildGraphKey(digest); try { await db.KeyDeleteAsync(graphKey).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); var endpoints = _redis.GetEndPoints(); if (endpoints.Length == 0) { _logger.LogWarning("No Redis endpoints available for key enumeration"); return; } var slicePattern = BuildSlicePattern(digest); var sliceKeys = new List(); foreach (var endpoint in endpoints) { cancellationToken.ThrowIfCancellationRequested(); var server = _redis.GetServer(endpoint); if (server is null || !server.IsConnected) { continue; } sliceKeys.AddRange(server.Keys( _options.Database, slicePattern, pageSize: DefaultScanPageSize)); } if (sliceKeys.Count > 0) { await db.KeyDeleteAsync(sliceKeys.ToArray()).ConfigureAwait(false); _logger.LogDebug("Invalidated {Count} slice keys for graph {Digest}", sliceKeys.Count, digest); } _logger.LogInformation("Invalidated cache for graph {Digest}", digest); } catch (OperationCanceledException) { throw; } catch (Exception ex) { _logger.LogWarning(ex, "Failed to invalidate cache for graph {Digest}", digest); } } }