Files
MoShiro/backend/src/Monitor.Backend.Application/Live/HotLayerBackfill.cs
T
2026-10-04 21:46:39 +08:00

68 lines
2.4 KiB
C#

using Fleet.Core;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Monitor.Protocol;
namespace Monitor.Backend.Application;
/// <summary>重启后热层为空:从原始块回填最近 1 小时(低优先级、逐块让出)。完成之前查询规划器不会选用热层。</summary>
public sealed partial class HotLayerBackfill : BackgroundService
{
private readonly HotLayer _hot;
private readonly IRawStore _raw;
private readonly TimeProvider _time;
private readonly ILogger<HotLayerBackfill> _logger;
public HotLayerBackfill(HotLayer hot, IRawStore raw, TimeProvider time, ILogger<HotLayerBackfill> 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();
}
}
/// <summary>回填(也供测试直接调用)。</summary>
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<RawChunkMeta> 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);
}