fix(auditlog)+fix(configdb): budget the ingest fallback loop; index + varchar-type the SiteCalls list query (arch-review 2026-08-19 P5-1 + P5-2)
P5-1: AuditLogIngestActor.OnCachedTelemetryAsync's per-entry fallback loop (used when the cached-telemetry batch fast path fails) ran with no budget/cancellation, unlike OnIngestAsync's fallback. A slow/wedged DB could freeze the central ingest singleton's mailbox for minutes. The loop now runs under its own fresh IngestFallbackBudget CancellationTokenSource (mirroring OnIngestAsync), passes the token into BeginTransactionAsync/ InsertIfNotExistsAsync/UpsertAsync, and stops early on budget exhaustion, logging how many entries were left for the periodic reconciliation pull to pick up. P5-2: SiteCallAuditRepository.QueryAsync ordered by (CreatedAtUtc DESC, TrackedOperationId DESC) with no index leading unconditionally on CreatedAtUtc, forcing a full scan+sort on an unfiltered page. Added IX_SiteCalls_Created_TrackedOperationId (migration AddSiteCallsListSortIndex + matching hand-written production-parity script under docs/plans/sql/). Also switched the query from FromSqlInterpolated (which binds string filters as nvarchar(4000)) to FromSqlRaw with explicitly SqlDbType.VarChar-typed parameters, matching the SiteCalls columns' IsUnicode(false) declaration and the write path's existing Param(...) pattern, so seeks — including the keyset predicate — are no longer blocked by an implicit conversion. Tests: new AuditLogIngestActorCombinedTelemetryTests case proves the fallback budget bounds the wait (hanging repo stub, ~5s not indefinite), that remaining entries are skipped, and that a non-default/cancellable token reaches the repository call; verified fail-unfixed by reverting the actor change (test times out at its 20s bound). Existing SiteCallAuditRepositoryTests QueryAsync/keyset/stuck-cutoff cases (real MSSQL) confirm the raw-SQL rewrite still returns correct pages.
This commit is contained in:
+12
@@ -106,5 +106,17 @@ public class SiteCallEntityTypeConfiguration : IEntityTypeConfiguration<SiteCall
|
||||
builder.HasIndex(s => s.TerminalAtUtc)
|
||||
.HasDatabaseName("IX_SiteCalls_Terminal")
|
||||
.HasFilter("[TerminalAtUtc] IS NOT NULL");
|
||||
|
||||
// Created_TrackedOperationId backs QueryAsync's list-page ORDER BY
|
||||
// (CreatedAtUtc DESC, TrackedOperationId DESC) and its matching keyset
|
||||
// predicate — the unfiltered/lightly-filtered Central UI Site Calls page.
|
||||
// None of the indexes above lead on CreatedAtUtc unconditionally
|
||||
// (Source_Created requires an equality on SourceSite first;
|
||||
// NonTerminal/Terminal are filtered to one side of TerminalAtUtc), so an
|
||||
// unfiltered page previously fell back to a full clustered-index scan +
|
||||
// sort (arch-review 2026-08-19 P5-2, register row-35 class).
|
||||
builder.HasIndex(s => new { s.CreatedAtUtc, s.TrackedOperationId })
|
||||
.IsDescending(true, true)
|
||||
.HasDatabaseName("IX_SiteCalls_Created_TrackedOperationId");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user