perf(backend): derive maintenance tenant list from TenantConfiguration, not a Sessions scan

GetAllTenantIdsAsync answered "which tenants exist?" by scanning the entire
Sessions table (PK projection) — an O(corpus) cost paid multiple times per 2h
maintenance run by all 6 callers (stalled/excessive sweeps, retention fanout,
platform-stats recompute, distress-report cleanup). It grows with session
history rather than tenant count.

Read the TenantConfiguration table instead (one "config" row per tenant, same
proven pattern as TableConfigRepository). Same return shape, so all callers
benefit with no interface or DI change.

Trade-off (documented in code): a fully-offboarded tenant whose config row was
deleted but whose old sessions still linger is no longer visited by maintenance
sweeps. Acceptable — the offboarding cascade removes those sessions anyway, and a
merely disabled tenant keeps its config row and is still processed.

Not bundled here: day-windowing RecomputePlatformStatsAsync — its counters are
cumulative/all-time and need separate semantic review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Oliver Kieselbach
2026-06-16 17:30:11 +02:00
parent ea81b1eca0
commit f67341dd70
@@ -616,14 +616,24 @@ namespace AutopilotMonitor.Functions.Services
}
/// <summary>
/// Gets all unique tenant IDs from sessions table
/// Gets all known tenant IDs from the TenantConfiguration table (one "config" row per
/// tenant). This replaces an O(corpus) scan of the entire Sessions table — the maintenance
/// pass only needs the tenant set, not every session, and the tenant set grows with the
/// number of onboarded tenants, not the session history.
///
/// Trade-off: a tenant whose config row was deleted (full offboarding) but whose old
/// sessions still linger will no longer be visited by maintenance sweeps. That is
/// acceptable — the offboarding cascade removes those sessions anyway, and a merely
/// disabled tenant keeps its config row and is still processed.
/// </summary>
public async Task<List<string>> GetAllTenantIdsAsync()
{
try
{
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.Sessions);
var query = tableClient.QueryAsync<TableEntity>(select: new[] { "PartitionKey" });
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.TenantConfiguration);
var query = tableClient.QueryAsync<TableEntity>(
filter: "RowKey eq 'config'",
select: new[] { "PartitionKey" });
var tenantIds = new HashSet<string>();
await foreach (var entity in query)