mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-25 05:00:07 +00:00
b37e6f975c
Cold start point-reads a SHA-256 over TableNames.All stored in AdminConfiguration/SchemaSentinel/tables instead of issuing 66 CreateTableIfNotExists calls. Missing/stale sentinel runs the full idempotent pass once and rewrites it. SessionsIndex startup backfill only after a full pass, behind a conditional-insert claim (one instance under scale-out); IsSessionIndexEmptyAsync no longer treats errors as empty. Daily maintenance runs IStorageInitializer.EnsureAllAsync to repair out-of-band table deletions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
67 lines
2.6 KiB
C#
67 lines
2.6 KiB
C#
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace AutopilotMonitor.Functions.Services;
|
|
|
|
/// <summary>
|
|
/// Hosted service that initializes all Azure Table Storage tables at application startup.
|
|
/// This ensures all tables exist before any requests are processed.
|
|
/// Also performs a one-time backfill of the SessionsIndex table on fresh storage (see StartAsync).
|
|
/// </summary>
|
|
public class TableInitializerService : IHostedService
|
|
{
|
|
private readonly TableStorageService _tableStorageService;
|
|
private readonly ILogger<TableInitializerService> _logger;
|
|
|
|
public TableInitializerService(
|
|
TableStorageService tableStorageService,
|
|
ILogger<TableInitializerService> logger)
|
|
{
|
|
_tableStorageService = tableStorageService;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task StartAsync(CancellationToken cancellationToken)
|
|
{
|
|
_logger.LogInformation("TableInitializerService starting");
|
|
|
|
var fullPassRan = false;
|
|
try
|
|
{
|
|
fullPassRan = await _tableStorageService.InitializeTablesAsync();
|
|
_logger.LogInformation("TableInitializerService completed - all tables ready (fullPass={FullPass})", fullPassRan);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "TableInitializerService failed to initialize tables");
|
|
// Don't throw - allow the application to start even if table creation fails
|
|
// Individual operations will fail gracefully with appropriate error messages
|
|
}
|
|
|
|
// One-time backfill: SessionsIndex can only be empty on fresh storage, which is exactly
|
|
// when the schema sentinel was missing and the full pass ran. Skipped on the fast path,
|
|
// and guarded by a conditional-insert claim so only one scaled-out instance scans.
|
|
if (!fullPassRan) return;
|
|
|
|
try
|
|
{
|
|
if (await _tableStorageService.IsSessionIndexEmptyAsync()
|
|
&& await _tableStorageService.TryClaimSessionIndexBackfillAsync())
|
|
{
|
|
_logger.LogInformation("SessionsIndex table is empty — starting startup backfill");
|
|
var count = await _tableStorageService.BackfillSessionIndexAsync();
|
|
_logger.LogInformation("Startup backfill completed: {Count} sessions indexed", count);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Startup session index backfill failed — maintenance backfill will catch up");
|
|
}
|
|
}
|
|
|
|
public Task StopAsync(CancellationToken cancellationToken)
|
|
{
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|