fix(historian): #683 review — bound the StartEnd pick joins to the window; usable picks re-check usability
- SqlServer StartEnd join-back: every pick join also carries the window predicate (forward >= @start AND < @end, reverse > @start AND <= @end). Keyed on the pick time alone a hash/merge/adaptive join read the tag's whole history (review: 43,657 vs 361 logical reads), and parameter sniffing reused that plan for every tag. Forced OPTION (HASH JOIN), 1d@1h on 6M rows: 152,524 -> 6,756 reads. - Usable picks (x/y) re-check the exact u predicate in the join, so a concurrent HistoryUpdate Replace that turned the picked sample Bad/Empty reads as absent (BadNoData), never a Bad row's value under Delta. - StartEndJoinBackTests (integration): 300k history rows either side of a 1 h window, forced HASH and MERGE joins, results identical to the unhinted plan and Start/End correct, session logical reads < 1,000 (183 bounded; 8,055 with the bound removed — the test fails then). - TagIndexTests: the refused-ADD-INDEX test asserts the Warning naming the ALTER. - Historian.md: re-measured table incl. long-history windows (output identical by EXCEPT both ways in all 7 cases) and the forced-join read counts.
This commit is contained in:
+18
-1
@@ -100,7 +100,24 @@ public sealed class TagIndexTests(QuestDbFixture fx)
|
||||
await using var alter = conn.CreateCommand(c, QuestDbTables.AddTagIndex(conn.SampleTable));
|
||||
await Should.ThrowAsync<Exception>(() => alter.ExecuteNonQueryAsync(Ct)); // precondition: really refused
|
||||
}
|
||||
await new QuestDbSchemaBootstrapper(conn, o).EnsureAsync(Ct);
|
||||
var log = new ListLogger();
|
||||
await new QuestDbSchemaBootstrapper(conn, o, log).EnsureAsync(Ct);
|
||||
log.Warnings.ShouldContain(m => m.Contains("Could not add the tag index", StringComparison.Ordinal)
|
||||
&& m.Contains(QuestDbTables.AddTagIndex(conn.SampleTable), StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
/// <summary>Captures the rendered Warning messages.</summary>
|
||||
private sealed class ListLogger : Microsoft.Extensions.Logging.ILogger
|
||||
{
|
||||
private readonly System.Collections.Concurrent.ConcurrentQueue<string> _warnings = new();
|
||||
public IReadOnlyCollection<string> Warnings => _warnings;
|
||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
|
||||
public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) => true;
|
||||
public void Log<TState>(Microsoft.Extensions.Logging.LogLevel logLevel, Microsoft.Extensions.Logging.EventId eventId,
|
||||
TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
if (logLevel == Microsoft.Extensions.Logging.LogLevel.Warning) _warnings.Enqueue(formatter(state, exception));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user