fix(historian): #694 R2 cap the crowding re-read room at MaxTieClusterOverfetch
The #687/F4 crowding re-read doubled its room up to 8 re-reads with no cap, so a pathological single-ms cluster on a streaming gateway was streamed several times over for one page. The room is now capped at ServerHistorian:MaxTieClusterOverfetch (plumbed into GatewayHistorianDataSource as maxPreWindowRows, default 65536); past it, or once a read has already seen more, the read is BadTooManyOperations with PreWindowRows set.
This commit is contained in:
+34
@@ -255,6 +255,40 @@ public sealed class GatewayRawBoundaryRowTests
|
||||
ds.GetHealthSnapshot().TotalFailures.ShouldBe(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task The_crowding_re_reads_never_make_room_for_more_than_the_tie_cluster_limit()
|
||||
{
|
||||
// #694 R2: the room used to double up to 8 re-reads with no cap — on a streaming gateway a pathological 1M-row
|
||||
// single-millisecond cluster streamed ~4M rows for one page where it used to fail at once. The room is capped at
|
||||
// MaxTieClusterOverfetch (here 100); past it the read is a request limit, after a bounded number of bounded reads.
|
||||
var margin = Enumerable.Range(0, 150).Select(i => (At(30).AddTicks(-1 - i), (double)i, false));
|
||||
var fake = Aveva(margin.Append((At(35), 1d, false)).ToList());
|
||||
var ds = new GatewayHistorianDataSource(fake, NullLogger<GatewayHistorianDataSource>.Instance, maxPreWindowRows: 100);
|
||||
|
||||
var ex = await Should.ThrowAsync<HistoryReadLimitExceededException>(() => ds.ReadRawAsync("T", At(30), At(40), 5, Ct));
|
||||
|
||||
ex.PreWindowRows.ShouldBeGreaterThan(100);
|
||||
// Room 21, 42, 84: that read already saw more than 100 rows before the window, so it stops — never asking past the cap.
|
||||
fake.ReadRawCallCount.ShouldBe(4);
|
||||
fake.RawCalls.Max(c => c.MaxValues).ShouldBeLessThanOrEqualTo(5 + 100 + RawAggregateReader.PreWindowAllowance);
|
||||
fake.RawYieldedCount.ShouldBeLessThanOrEqualTo(21 + 42 + 63 + 105);
|
||||
fake.RawStreamsCompleted.ShouldBe(4);
|
||||
ds.GetHealthSnapshot().TotalFailures.ShouldBe(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Crowding_rows_just_under_the_tie_cluster_limit_are_read_around()
|
||||
{
|
||||
var margin = Enumerable.Range(0, 90).Select(i => (At(30).AddTicks(-1 - i), (double)i, false));
|
||||
var fake = Aveva(margin.Append((At(35), 1d, false)).ToList());
|
||||
var ds = new GatewayHistorianDataSource(fake, NullLogger<GatewayHistorianDataSource>.Instance, maxPreWindowRows: 100);
|
||||
|
||||
var r = await ds.ReadRawAsync("T", At(30), At(40), 5, Ct);
|
||||
|
||||
r.Samples.Select(s => s.Value).ShouldBe([1d]);
|
||||
fake.RawCalls.Max(c => c.MaxValues).ShouldBeLessThanOrEqualTo(5 + 100 + RawAggregateReader.PreWindowAllowance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_short_read_is_complete_and_is_not_re_read()
|
||||
{
|
||||
|
||||
+13
@@ -131,4 +131,17 @@ public sealed class HistorianGatewayClientAdapterTests
|
||||
var dataSource = Assert.IsType<GatewayHistorianDataSource>(GatewayHistorian.CreateDataSource(opts, services));
|
||||
dataSource.AtTimeFromRaw.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[Theory] // #694 R2: ServerHistorian:MaxTieClusterOverfetch bounds the crowding re-reads (default 65536)
|
||||
[InlineData(null, 65536)]
|
||||
[InlineData(1000, 1000)]
|
||||
public void Factory_passes_the_tie_cluster_limit_to_the_crowding_re_reads(int? configured, int expected)
|
||||
{
|
||||
var opts = configured is { } c
|
||||
? new ServerHistorianOptions { Enabled = true, Endpoint = "https://localhost:5222", ApiKey = "histgw_x_y", MaxTieClusterOverfetch = c }
|
||||
: new ServerHistorianOptions { Enabled = true, Endpoint = "https://localhost:5222", ApiKey = "histgw_x_y" };
|
||||
using var services = new ServiceCollection().BuildServiceProvider();
|
||||
var dataSource = Assert.IsType<GatewayHistorianDataSource>(GatewayHistorian.CreateDataSource(opts, services));
|
||||
dataSource.MaxPreWindowRows.ShouldBe(expected);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user