feat(historian): SQL Server retention purge (RetentionDays, Primary-gated, applock) (#667)

This commit is contained in:
Joseph Doherty
2026-10-11 08:21:30 -04:00
parent db6a53036b
commit 924eff8d3c
13 changed files with 602 additions and 2 deletions
@@ -22,6 +22,21 @@ public sealed partial class SqlServerHistorianOptions
/// </summary>
public bool AutoCreateSchema { get; set; } = true;
/// <summary>
/// Days of history to keep, measured on source time. <c>null</c> (default) keeps everything. When set,
/// a driver node deletes older <c>hist.Sample</c>/<c>hist.Event</c> rows every <see cref="PurgeInterval"/>.
/// </summary>
public int? RetentionDays { get; set; }
/// <summary>How often the retention purge runs. Must be positive.</summary>
public TimeSpan PurgeInterval { get; set; } = TimeSpan.FromHours(1);
/// <summary>
/// Rows per <c>DELETE TOP(n)</c> batch. Default 4000 stays under SQL Server's 5000-lock escalation
/// threshold so a purge never escalates to a table lock.
/// </summary>
public int PurgeBatchSize { get; set; } = 4000;
[GeneratedRegex("^[A-Za-z_][A-Za-z0-9_]{0,127}$")]
private static partial Regex IdentifierRegex();
@@ -41,6 +56,12 @@ public sealed partial class SqlServerHistorianOptions
errors.Add("ServerHistorian:SqlServer:Schema must be a SQL identifier matching ^[A-Za-z_][A-Za-z0-9_]{0,127}$.");
if (CommandTimeout <= TimeSpan.Zero)
errors.Add("ServerHistorian:SqlServer:CommandTimeout must be greater than zero.");
if (RetentionDays is < 1)
errors.Add("ServerHistorian:SqlServer:RetentionDays must be at least 1 when set (omit it to keep history forever).");
if (PurgeInterval <= TimeSpan.Zero)
errors.Add("ServerHistorian:SqlServer:PurgeInterval must be greater than zero.");
if (PurgeBatchSize < 1)
errors.Add("ServerHistorian:SqlServer:PurgeBatchSize must be at least 1.");
return errors;
}
}