|
|
|
@@ -9,7 +9,7 @@ namespace ZB.MOM.WW.ScadaBridge.SiteEventLogging;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Predicate the <see cref="EventLogPurgeService"/> consults at the top of every purge
|
|
|
|
|
/// tick. As of arch-review 2026-08-19 P4-4 it no longer GATES the purge — DataConnectionActor
|
|
|
|
|
/// tick. It no longer GATES the purge — DataConnectionActor
|
|
|
|
|
/// writes <c>site_events</c> on both site nodes (register rows 89/90/97), so a standby that
|
|
|
|
|
/// skipped its own purge would grow its file unboundedly on an unreplicated site. Every node
|
|
|
|
|
/// now purges its own file every tick; this predicate is consulted only to annotate the tick's
|
|
|
|
@@ -116,7 +116,7 @@ public class EventLogPurgeService : BackgroundService
|
|
|
|
|
/// <param name="logger">Logger instance.</param>
|
|
|
|
|
/// <param name="isActiveNode">
|
|
|
|
|
/// Optional active-node check, consulted only to annotate the purge tick's log line
|
|
|
|
|
/// (arch-review 2026-08-19 P4-4 removed the early-exit this used to drive — every node
|
|
|
|
|
/// (there is no early-exit — every node
|
|
|
|
|
/// purges its own <c>site_events</c> file every tick, since DataConnectionActor writes
|
|
|
|
|
/// on both site nodes and a standby that skipped its own purge would grow unbounded on
|
|
|
|
|
/// an unreplicated site). When <c>null</c> every tick logs as active.
|
|
|
|
@@ -144,18 +144,16 @@ public class EventLogPurgeService : BackgroundService
|
|
|
|
|
_isReplicationConfigured = isReplicationConfigured ?? (static () => false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Runs the periodic purge loop until the host stops. Deliberately a delay loop rather
|
|
|
|
|
// than a PeriodicTimer: the wait between ticks is no longer a constant. A tick that
|
|
|
|
|
// stopped at SiteEventLogOptions.MaxPurgedRowsPerTick still has expired rows to delete,
|
|
|
|
|
// and waiting a full SiteEventLogOptions.PurgeInterval (a day) before resuming would make
|
|
|
|
|
// a paced purge take longer to converge than the retention window it enforces.
|
|
|
|
|
// Consequence worth knowing: the cadence is anchored to the END of the previous purge
|
|
|
|
|
// rather than to process start, so a long purge shifts the next tick by its own
|
|
|
|
|
// duration. At the daily interval and a measured ~17 s for a full 250 000-row tick that
|
|
|
|
|
// drift is immaterial.
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// Deliberately a delay loop rather than a <see cref="PeriodicTimer"/>: the wait between
|
|
|
|
|
/// ticks is no longer a constant. A tick that stopped at
|
|
|
|
|
/// <see cref="SiteEventLogOptions.MaxPurgedRowsPerTick"/> still has expired rows to delete,
|
|
|
|
|
/// and waiting a full <see cref="SiteEventLogOptions.PurgeInterval"/> (a day) before
|
|
|
|
|
/// resuming would make a paced purge take longer to converge than the retention window it
|
|
|
|
|
/// enforces. Consequence worth knowing: the cadence is now anchored to the END of the
|
|
|
|
|
/// previous purge rather than to process start, so a long purge shifts the next tick by its
|
|
|
|
|
/// own duration. At the daily interval and a measured ~17 s for a full 250 000-row tick that
|
|
|
|
|
/// drift is immaterial.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogInformation(
|
|
|
|
@@ -164,7 +162,7 @@ public class EventLogPurgeService : BackgroundService
|
|
|
|
|
_options.RetentionDays, _options.MaxStorageMb, _options.PurgeInterval,
|
|
|
|
|
_options.MaxPurgedRowsPerTick > 0 ? _options.MaxPurgedRowsPerTick.ToString() : "unlimited");
|
|
|
|
|
|
|
|
|
|
// arch-review 2026-08-19 P4-5: yield BEFORE the initial (potentially ~17s
|
|
|
|
|
// Yield BEFORE the initial (potentially ~17s
|
|
|
|
|
// ceiling-limited, ~285s unpaced worst case) RunPurge() rather than calling it
|
|
|
|
|
// as the first synchronous statement. .NET 8+'s BackgroundService already invokes
|
|
|
|
|
// ExecuteAsync via Task.Run, so a synchronous prelude here does not actually block
|
|
|
|
@@ -205,7 +203,7 @@ public class EventLogPurgeService : BackgroundService
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// arch-review 2026-08-19 P4-4: purge no longer gates on the active-node check.
|
|
|
|
|
// Purge no longer gates on the active-node check.
|
|
|
|
|
// DataConnectionActor writes site_events on BOTH site nodes (register rows
|
|
|
|
|
// 89/90/97), so the old "standby receives no writes" premise this gate was
|
|
|
|
|
// built on is stale — a standby that never purges its own file grows
|
|
|
|
@@ -226,8 +224,8 @@ public class EventLogPurgeService : BackgroundService
|
|
|
|
|
}
|
|
|
|
|
catch (Exception checkEx)
|
|
|
|
|
{
|
|
|
|
|
// arch-review 2026-08-19 P4-5: fail CLOSED, not "active". This predicate no
|
|
|
|
|
// longer gates any work (P4-4 — both nodes purge their own file every tick
|
|
|
|
|
// Fail CLOSED, not "active". This predicate no
|
|
|
|
|
// longer gates any work (both nodes purge their own file every tick
|
|
|
|
|
// regardless), so a wrong value here cannot skip a purge; but silently
|
|
|
|
|
// reporting "active" for a check that just threw is dishonest, and an
|
|
|
|
|
// operator correlating this line against the real active-node state would be
|
|
|
|
@@ -536,7 +534,7 @@ public class EventLogPurgeService : BackgroundService
|
|
|
|
|
var rows = cmd.ExecuteNonQuery();
|
|
|
|
|
|
|
|
|
|
// Reclaim free pages so page_count/freelist measurement reflects the delete.
|
|
|
|
|
// arch-review 2026-08-19 P4-6: this is effective ONLY on a consolidated
|
|
|
|
|
// This is effective ONLY on a consolidated
|
|
|
|
|
// LocalDb file that was FRESH when SiteLocalDbSetup.EnsureAutoVacuumOnFreshFile
|
|
|
|
|
// ran (Host/SiteLocalDbSetup.cs) — auto_vacuum can only be enabled before any
|
|
|
|
|
// table exists, and SqliteLocalDb's constructor creates LocalDb's own
|
|
|
|
@@ -676,7 +674,7 @@ public class EventLogPurgeService : BackgroundService
|
|
|
|
|
/// <para>
|
|
|
|
|
/// <b>Clamped by the file.</b> The whole-file logical size is a true upper bound on any one
|
|
|
|
|
/// table's pages, so the model is capped by it. That can only ever correct an
|
|
|
|
|
/// over-estimate downward; it cannot reintroduce the row-81 defect, because on a replicated
|
|
|
|
|
/// over-estimate downward; it cannot reintroduce the whole-file measurement defect, because on a replicated
|
|
|
|
|
/// node the file is larger than <c>site_events</c>, not smaller, and so never binds.
|
|
|
|
|
/// </para>
|
|
|
|
|
/// <para>
|
|
|
|
|