using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace StellaOps.Provcache; public sealed partial class ProvcacheService { /// public async Task GetAsync( string veriKey, bool bypassCache = false, CancellationToken cancellationToken = default) { ArgumentException.ThrowIfNullOrWhiteSpace(veriKey); if (bypassCache && _options.AllowCacheBypass) { _logger.LogDebug("Cache bypass requested for VeriKey {VeriKey}", veriKey); return ProvcacheServiceResult.Bypassed(); } var sw = Stopwatch.StartNew(); using var activity = ProvcacheTelemetry.StartGetActivity(veriKey); try { var result = await _store.GetAsync(veriKey, cancellationToken).ConfigureAwait(false); sw.Stop(); RecordMetrics(result.IsHit, sw.Elapsed.TotalMilliseconds); if (result.IsHit && result.Entry is not null) { if (result.Entry.ExpiresAt <= _timeProvider.GetUtcNow()) { _logger.LogDebug("Cache entry for VeriKey {VeriKey} is expired", veriKey); ProvcacheTelemetry.RecordRequest("get", "expired"); return ProvcacheServiceResult.Expired(result.Entry, sw.Elapsed.TotalMilliseconds); } _logger.LogDebug( "Cache hit for VeriKey {VeriKey} from {Source} in {ElapsedMs}ms", veriKey, result.Source, sw.Elapsed.TotalMilliseconds); ProvcacheTelemetry.MarkCacheHit(activity, result.Source ?? "valkey"); ProvcacheTelemetry.RecordHit(result.Source ?? "valkey"); ProvcacheTelemetry.RecordRequest("get", ProvcacheTelemetry.ResultHit); ProvcacheTelemetry.RecordLatency("get", sw.Elapsed); return ProvcacheServiceResult.Hit(result.Entry, result.Source!, sw.Elapsed.TotalMilliseconds); } var dbEntry = await _repository.GetAsync(veriKey, cancellationToken).ConfigureAwait(false); sw.Stop(); if (dbEntry is not null) { if (dbEntry.ExpiresAt <= _timeProvider.GetUtcNow()) { _logger.LogDebug("Database entry for VeriKey {VeriKey} is expired", veriKey); ProvcacheTelemetry.RecordRequest("get", "expired"); return ProvcacheServiceResult.Expired(dbEntry, sw.Elapsed.TotalMilliseconds); } await _store.SetAsync(dbEntry, cancellationToken).ConfigureAwait(false); _logger.LogDebug( "Cache backfill for VeriKey {VeriKey} from postgres in {ElapsedMs}ms", veriKey, sw.Elapsed.TotalMilliseconds); ProvcacheTelemetry.MarkCacheHit(activity, "postgres"); ProvcacheTelemetry.RecordHit("postgres"); ProvcacheTelemetry.RecordRequest("get", ProvcacheTelemetry.ResultHit); ProvcacheTelemetry.RecordLatency("get", sw.Elapsed); return ProvcacheServiceResult.Hit(dbEntry, "postgres", sw.Elapsed.TotalMilliseconds); } ProvcacheTelemetry.MarkCacheMiss(activity); ProvcacheTelemetry.RecordMiss(); ProvcacheTelemetry.RecordRequest("get", ProvcacheTelemetry.ResultMiss); ProvcacheTelemetry.RecordLatency("get", sw.Elapsed); _logger.LogDebug("Cache miss for VeriKey {VeriKey} in {ElapsedMs}ms", veriKey, sw.Elapsed.TotalMilliseconds); return ProvcacheServiceResult.Miss(sw.Elapsed.TotalMilliseconds); } catch (Exception ex) { ProvcacheTelemetry.MarkError(activity, ex.Message); ProvcacheTelemetry.RecordRequest("get", ProvcacheTelemetry.ResultError); _logger.LogError(ex, "Error getting cache entry for VeriKey {VeriKey}", veriKey); return ProvcacheServiceResult.Miss(sw.Elapsed.TotalMilliseconds); } } }