using Fleet.Core; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Monitor.Protocol; namespace Monitor.Backend.Application; /// 重启后热层为空:从原始块回填最近 1 小时(低优先级、逐块让出)。完成之前查询规划器不会选用热层。 public sealed partial class HotLayerBackfill : BackgroundService { private readonly HotLayer _hot; private readonly IRawStore _raw; private readonly TimeProvider _time; private readonly ILogger _logger; public HotLayerBackfill(HotLayer hot, IRawStore raw, TimeProvider time, ILogger logger) { _hot = hot; _raw = raw; _time = time; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { try { await RunAsync(stoppingToken); } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { // 正常停止 } catch (Exception ex) { // 回填失败不应该让热层永远不可用:标记就绪,缺的部分会通过降级到 10s 层之外的查询体现 LogFailed(ex); _hot.MarkReady(); } } /// 回填(也供测试直接调用)。 public async Task RunAsync(CancellationToken ct) { long now = _time.GetUtcNow().ToUnixTimeMilliseconds(); long from = now - (long)HotLayer.Retention.TotalMilliseconds - 600_000; foreach ((NodeId agent, string series) in await _raw.ListSeriesAsync(from, now, ct)) { IReadOnlyList metas = await _raw.FindAsync(new RawQuery(agent, series, FromMs: from, ToMs: now, Limit: 2000), ct); foreach (RawChunkMeta meta in metas.Where(m => m.TestId is null)) { byte[]? bytes = await _raw.ReadAsync(meta, ct); if (bytes is not null && RawChunkCodec.Decode(bytes).TryGet(out RawChunk? chunk, out _)) { _hot.Merge(agent, series, chunk); } await Task.Yield(); } } _hot.MarkReady(); } [LoggerMessage(Level = LogLevel.Error, Message = "hot layer backfill failed")] private partial void LogFailed(Exception exception); }