using Microsoft.Extensions.Configuration; namespace ZB.MOM.WW.ScadaBridge.AuditLog.Central; /// /// Tuning knobs for the central singleton. /// Default cadence is 24 hours; the retention window itself /// is sourced from /// (default 365) so operators tune retention from a single section. /// /// /// /// The purge actor is a daily-cadence singleton, not a hot-loop, because /// partition-switch I/O is metadata-only but the drop-and-rebuild dance /// briefly removes the UX_AuditLog_EventId unique index — running /// more often than necessary trades index-rebuild outages for marginal /// freshness gains. Lower this only when an operator can prove they need /// sub-daily purge granularity. /// /// /// exists for tests to drop the cadence to /// milliseconds; production config is expected to set /// only. Because this options class is Bind-ed wholesale, a config value /// at AuditLog:Purge:IntervalOverride would bind if present (and would /// bypass the minimum clamp) — operators must not set it. /// /// public sealed class AuditLogPurgeOptions { /// Period of the purge tick in hours (default 24). public int IntervalHours { get; set; } = 24; /// /// Batch size for the per-channel retention-override row DELETE /// (). /// Each DELETE TOP (@batch) caps the transaction-log and lock footprint /// per statement; the repository loops batches until no rows remain. Default /// 5000 keeps individual deletes short on a busy central DB while still draining /// a large backlog within a tick. Clamped to a sane minimum in /// . /// /// /// The operator-facing config key is ChannelPurgeBatchSize /// (per Component-AuditLog.md), so the binder maps that documented key onto this /// backing property via . The unattributed /// property name (ChannelPurgeBatchSizeConfigured) would otherwise have been /// the bind key, silently ignoring the documented section. /// [ConfigurationKeyName("ChannelPurgeBatchSize")] public int ChannelPurgeBatchSizeConfigured { get; set; } = 5000; /// /// Resolves the effective per-channel purge batch size, clamped to at least 1 so /// a misconfigured 0/negative value cannot make the repository's DELETE /// loop spin or throw. /// public int ChannelPurgeBatchSize => ChannelPurgeBatchSizeConfigured < 1 ? 1 : ChannelPurgeBatchSizeConfigured; /// /// Per-command timeout (in minutes) for the maintenance SQL the purge tick issues — /// both the partition switch-out drop-and-rebuild dance /// () /// and each per-channel DELETE TOP batch. Default 30 minutes. /// /// /// The ADO.NET default command timeout is ~30 seconds. On a large or contended partition the /// SWITCH dance (which briefly drops UX_AuditLog_EventId) can exceed that and abort /// mid-flight — leaving the live table without its idempotency-supporting unique index until a /// later tick's CATCH branch rebuilds it (arch-review 04, S2). A generous maintenance timeout /// lets the metadata-only switch complete rather than self-locking. Resolved via /// , clamped to a 1-minute floor. /// public int MaintenanceCommandTimeoutMinutes { get; set; } = 30; /// /// Resolves the effective maintenance command timeout, clamped to at least 1 minute so a /// misconfigured 0/negative value cannot yield a zero/negative timeout (which ADO.NET /// interprets as "no timeout" for 0 and rejects for negatives). /// public TimeSpan ResolvedMaintenanceCommandTimeout => TimeSpan.FromMinutes(MaintenanceCommandTimeoutMinutes < 1 ? 1 : MaintenanceCommandTimeoutMinutes); /// /// Test-only override for finer control over the tick cadence than /// whole-hour resolution allows. When non-null, takes precedence over /// AND bypasses the /// minimum clamp (so tests can use millisecond cadences). Production /// config exposes only and never sets this /// knob — but because the options class is Bind-ed wholesale, a /// config value at AuditLog:Purge:IntervalOverride WOULD bind if /// present; operators must not set it. /// public TimeSpan? IntervalOverride { get; set; } /// /// Minimum interval the config-bound can /// resolve to. Clamps a misconfigured IntervalHours: 0 (or a /// negative value) away from — a zero /// interval would make Akka's ScheduleTellRepeatedlyCancelable /// spin, looping the partition drop/rebuild dance into a sustained SQL /// outage. The test-only bypasses this /// clamp so unit tests can still drop the cadence to milliseconds. /// private static readonly TimeSpan MinConfiguredInterval = TimeSpan.FromMinutes(1); /// /// Resolves the effective tick interval, honouring the test override /// when set. Falls back to , clamped to at /// least so a zero/negative config /// value can never yield (which would spin /// the scheduler). /// public TimeSpan Interval { get { if (IntervalOverride is { } overrideValue) { return overrideValue; } var resolved = TimeSpan.FromHours(IntervalHours); return resolved < MinConfiguredInterval ? MinConfiguredInterval : resolved; } } }