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. The /// partition switch itself is metadata-only (since the /// AlignAuditLogEventIdUniqueness migration it no longer drops and rebuilds a /// non-aligned unique index around it), but it still takes schema-modification /// locks and competes with ingest for the same table. 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; /// Maximum rows deleted for each channel in one purge tick (default 50,000); remaining backlog resumes on later ticks. public int MaxPurgedRowsPerChannelPerTick { get; set; } = 50_000; /// /// 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 stops at /// so a backlogged channel yields to later channels. Default 5000 keeps individual /// deletes short on a busy central DB. 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 staging batch /// () /// 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 batch can exceed that and abort mid-flight, leaving an orphaned staging table for the /// next tick's CATCH branch to clean up (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; } } }