using System.Text.Json; using Microsoft.EntityFrameworkCore; using StackExchange.Redis; public class ThresholdCacheLoader : IHostedService { private readonly IServiceProvider _services; private readonly IConnectionMultiplexer _redis; private readonly ILogger _logger; public ThresholdCacheLoader( IServiceProvider services, IConnectionMultiplexer redis, ILogger logger) { _services = services; _redis = redis; _logger = logger; } public async Task StartAsync(CancellationToken cancellationToken) { using var scope = _services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); await RefreshAsync(db, _redis, _logger, cancellationToken); } public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; /// /// Reloads all gateway alert thresholds into Redis. Called on startup and after /// pulls thresholds from central. /// public static async Task RefreshAsync( GatewayDbContext db, IConnectionMultiplexer redis, ILogger logger, CancellationToken cancellationToken = default) { var thresholds = await db.AlertThresholds.ToListAsync(cancellationToken); if (thresholds.Count == 0) { logger.LogWarning("No alert thresholds in gateway DB — Redis cache not updated"); return; } const int maxAttempts = 3; int[] backoffMs = [2000, 4000, 8000]; for (var attempt = 0; attempt < maxAttempts; attempt++) { try { WriteThresholds(redis, thresholds); logger.LogInformation( "Loaded {Count} alert thresholds into gateway Redis cache", thresholds.Count); return; } catch (RedisException ex) { logger.LogWarning(ex, "Redis unavailable during gateway threshold cache load — attempt {Attempt}/{Max}", attempt + 1, maxAttempts); if (attempt < maxAttempts - 1) await Task.Delay(backoffMs[attempt], cancellationToken); } } logger.LogError( "Failed to load thresholds into gateway Redis after {Max} attempts — " + "observation ingest falls back to PostgreSQL for threshold lookups", maxAttempts); } private static void WriteThresholds(IConnectionMultiplexer redis, List thresholds) { var cache = redis.GetDatabase(); var batch = cache.CreateBatch(); foreach (var t in thresholds) { var json = JsonSerializer.Serialize(new ThresholdCacheEntry( t.ObservationCode, t.CriticalLow, t.WarningLow, t.WarningHigh, t.CriticalHigh)); _ = batch.StringSetAsync($"threshold:{t.ObservationCode}", json); } batch.Execute(); } }