fix(historian): #691.4 re-review — never narrow a walk segment below the 1 ms request granularity
A tie cluster's truncated read left the width at one tick; once the gap above it was under two milliseconds nothing floored it, so segHi stepped down one tick per read: 30 rows at one ms 0.3 s below hi with a carried 1 s width took 10,006 identical reads (S8). (a) The top-anchored width is floored at the granularity, Min kept; (b) a segment no wider than the granularity is the unit that escalates to MaxTieClusterOverfetch and then refuses - per millisecond, exact for the ms-resolution Gateway (documented). Both are needed: the floor alone loops on crowded reads with a small tie budget. S8 is a repo test (4 shapes, reads <= 30, 0 repeated requests); random-data tests now measure the refusal unit as rows in any 1 ms window. Reviewer harness, 5,000 seeds x default/MSONLY/CROWD: worst page 70 reads (default/MSONLY), 63 (CROWD) at M=65536, p99.9 27-37; no page over 500 reads at any tie budget.
This commit is contained in:
+20
-2
@@ -446,20 +446,38 @@ public sealed class NodeManagerHistoryReadEventsPagingTests : IDisposable
|
||||
var expected = events.OrderByDescending(e => e.EventTimeUtc).ThenByDescending(e => e.EventId, StringComparer.Ordinal)
|
||||
.Select(e => e.EventId).ToList();
|
||||
|
||||
// The walk refuses more than MaxTieClusterOverfetch events within ONE MILLISECOND (#691.4 re-review): with the
|
||||
// small budget a dense sub-millisecond burst is a legitimate BadTooManyOperations — and nothing else is.
|
||||
var times = events.Select(e => e.EventTimeUtc.Ticks).Order().ToList();
|
||||
var msMax = 0;
|
||||
for (int i = 0, j = 0; i < times.Count; i++)
|
||||
{
|
||||
while (times[i] - times[j] >= TimeSpan.TicksPerMillisecond) j++;
|
||||
msMax = Math.Max(msMax, i - j + 1);
|
||||
}
|
||||
|
||||
var all = new List<string>();
|
||||
byte[]? cp = null;
|
||||
var pages = 0;
|
||||
var refused = false;
|
||||
do
|
||||
{
|
||||
var r = ReadEvents(nm, node, max, cp, end, DateTime.MinValue);
|
||||
r.Error.StatusCode.Code.ShouldBe(StatusCodes.Good, $"seed {seed}");
|
||||
if (r.Error.StatusCode.Code == StatusCodes.BadTooManyOperations && msMax > nm.MaxTieClusterOverfetch)
|
||||
{
|
||||
refused = true;
|
||||
break;
|
||||
}
|
||||
|
||||
r.Error.StatusCode.Code.ShouldBe(StatusCodes.Good, $"seed {seed}, ms max {msMax}");
|
||||
all.AddRange(r.Ids);
|
||||
cp = r.Cp;
|
||||
(++pages).ShouldBeLessThan(2000);
|
||||
}
|
||||
while (cp is not null);
|
||||
|
||||
all.ShouldBe(expected, $"seed {seed}, N = {max}");
|
||||
if (refused) all.ShouldBe(expected.Take(all.Count), $"seed {seed}: what came before the refusal");
|
||||
else all.ShouldBe(expected, $"seed {seed}, N = {max}");
|
||||
}
|
||||
|
||||
await host.DisposeAsync();
|
||||
|
||||
Reference in New Issue
Block a user