fix(site-call-audit): reconciliation pull serves only un-acked tracking rows (node-local ack ledger), batched page upsert, prune removed sites' cursors (review 2026-10-21 06/PERF-2 + lane H follow-up)
This commit is contained in:
@@ -42,6 +42,27 @@ public interface ISiteCallAuditRepository
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
Task UpsertAsync(SiteCall siteCall, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Applies <see cref="UpsertAsync"/>'s exact per-row semantics (insert-if-absent, then
|
||||
/// the monotonic rank-guarded update, ownership-checked) to a whole page of rows in as
|
||||
/// few round trips as the implementation can manage — the reconciliation pull's write
|
||||
/// path (review 2026-10-21 06/PERF-2). Rows are applied in order; a row that faults
|
||||
/// (e.g. an ownership violation) surfaces as the same exception <see cref="UpsertAsync"/>
|
||||
/// would throw — the SQL Server implementation applies the rest of the page first. The
|
||||
/// default implementation simply upserts row by row and stops at the first fault.
|
||||
/// </summary>
|
||||
/// <param name="siteCalls">The rows to insert or monotonically update.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
async Task UpsertManyAsync(IReadOnlyList<SiteCall> siteCalls, CancellationToken ct = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(siteCalls);
|
||||
foreach (var siteCall in siteCalls)
|
||||
{
|
||||
await UpsertAsync(siteCall, ct).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the row for the given id, or <c>null</c> if none exists.
|
||||
/// </summary>
|
||||
|
||||
@@ -150,6 +150,15 @@ public interface IOperationTrackingStore
|
||||
/// deterministic — <c>UpdatedAtUtc ASC, TrackedOperationId ASC</c>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Only rows central has not already acknowledged.</b> A row whose
|
||||
/// current version (<c>UpdatedAtUtc</c>) central acknowledged through the
|
||||
/// cached-telemetry push — recorded via <see cref="RecordCentralAcksAsync"/> — is
|
||||
/// skipped: central already holds exactly what this read would send. The pull
|
||||
/// therefore carries only lost telemetry, not a replay of every lifecycle step
|
||||
/// (review 2026-10-21 06/PERF-2). The ack ledger is node-local, so a node that
|
||||
/// has just become active serves every row once.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="SiteCallOperational.SourceSite"/> is left as the empty string:
|
||||
/// the site id is not a tracking-store column, and the central client re-stamps
|
||||
/// it from the <c>siteId</c> it dialed (the only authority that knows which
|
||||
@@ -174,4 +183,24 @@ public interface IOperationTrackingStore
|
||||
int batchSize,
|
||||
string? afterId = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Records that central accepted the cached-telemetry push of each operation at the
|
||||
/// given tracking-row version (the snapshot's <c>UpdatedAtUtc</c> the push carried),
|
||||
/// so <see cref="ReadChangedSinceAsync"/> stops serving that version to the
|
||||
/// reconciliation pull. Monotonic per operation (an older version never replaces a
|
||||
/// newer one) and best-effort: a lost ack only means the row is pulled once more,
|
||||
/// which central's idempotent upsert absorbs.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The ledger lives in a NODE-LOCAL side table, never a column on the replicated
|
||||
/// tracking table. The default implementation records nothing (every row stays
|
||||
/// eligible for the pull — the pre-ack behaviour).
|
||||
/// </remarks>
|
||||
/// <param name="ackedVersions">Operation id → the <c>UpdatedAtUtc</c> of the snapshot central accepted.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>A task that completes once the acks are queued.</returns>
|
||||
Task RecordCentralAcksAsync(
|
||||
IReadOnlyDictionary<TrackedOperationId, DateTime> ackedVersions,
|
||||
CancellationToken ct = default) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user