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:
@@ -44,7 +44,7 @@ internal static class ReverseEventsReader
|
||||
ev => ev.EventTimeUtc.Ticks, EventIdTieOrder.Instance, hi, lo, want, budget, source.TimestampResolutionTicks,
|
||||
nowUtc, hint,
|
||||
at => new HistoryReadLimitExceededException(budget,
|
||||
$"A reverse events read of '{sourceName}' at {at:O} found more than {budget} events at one timestamp; the " +
|
||||
$"A reverse events read of '{sourceName}' at {at:O} found more than {budget} events within one millisecond; the " +
|
||||
"backend reads forward only, so the latest of them cannot be selected. Increase " +
|
||||
"ServerHistorian:MaxTieClusterOverfetch."),
|
||||
ct)
|
||||
|
||||
@@ -71,7 +71,7 @@ internal static class ReverseRawReader
|
||||
},
|
||||
Ticks, tieOrder: null, hi, lo, maxValues, budget, source.TimestampResolutionTicks, nowUtc, hint,
|
||||
at => new HistoryReadLimitExceededException(budget,
|
||||
$"A reverse raw read of '{tagname}' at {at:O} found more than {budget} samples at one timestamp; the " +
|
||||
$"A reverse raw read of '{tagname}' at {at:O} found more than {budget} samples within one millisecond; the " +
|
||||
"backend reads forward only, so the latest of them cannot be selected. Increase " +
|
||||
"ServerHistorian:MaxTieClusterOverfetch."),
|
||||
ct)
|
||||
|
||||
@@ -54,8 +54,9 @@ internal readonly record struct ForwardSegmentRead<T>(IReadOnlyList<T> Rows, boo
|
||||
/// with room for them (at most twice the budget + 4); otherwise the segment is narrowed — to the density the truncated
|
||||
/// read showed, and never again below the newest row it saw (<c>known</c>: rows exist there, so walking past it before
|
||||
/// finding what lies above it would re-read the same dense stretch over and over; the gap above it is bisected
|
||||
/// instead). Only a single timestamp holding more than <c>M</c> rows — a one-resolution segment that still does not
|
||||
/// fit at the tie budget — is refused.
|
||||
/// instead). Only more than <c>M</c> rows within one millisecond — a segment no wider than the 1 ms request
|
||||
/// granularity that still does not fit at the tie budget — is refused (per millisecond, not per timestamp: narrower
|
||||
/// requests are the same request, and the Gateway stores milliseconds).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Proving the rest empty (#691).</b> Walking down to the floor through empty history used to take one read per ×4
|
||||
@@ -194,12 +195,13 @@ internal static class ReverseWalk
|
||||
var gap = segHi - known;
|
||||
lowered = lowerBy > 0 && lowerBy < gap;
|
||||
bisected = !lowered && (bisecting || lowerBy > 0) && gap > 2 * Math.Max(granularity, knownWidth);
|
||||
// Never narrower than the request granularity when halving: requests are rounded outward to it, so a
|
||||
// narrower half re-sends the same request (#691.4 review). Keep the Min: a truncated read crowded by rows
|
||||
// before the segment must still shrink the segment.
|
||||
// Never narrower than the request granularity: requests are rounded outward to it, so a narrower segment
|
||||
// re-sends the same request — and a width left at one tick (a tie cluster's) stepped segHi down a tick per
|
||||
// read, ~10,000 identical reads (#691.4 re-review). Keep the Min: a truncated read crowded by rows before
|
||||
// the segment must still shrink the segment.
|
||||
w = lowered ? gap - lowerBy
|
||||
: bisected ? gap / 2
|
||||
: Math.Min(w, gap <= 2 * granularity ? gap : gap / 2);
|
||||
: Math.Min(Math.Max(w, granularity), gap <= 2 * granularity ? gap : gap / 2);
|
||||
}
|
||||
var segLo = segHi - stopTicks <= w ? stopTicks : segHi - w;
|
||||
var start = Utc(AlignDown(segLo, granularity));
|
||||
@@ -248,10 +250,12 @@ internal static class ReverseWalk
|
||||
continue;
|
||||
}
|
||||
|
||||
if (segHi - segLo <= resolution)
|
||||
if (segHi - segLo <= granularity)
|
||||
{
|
||||
// A one-resolution segment still over its budget: one timestamp's tie cluster. Escalate to the tie
|
||||
// budget M before refusing — a cluster of up to M rows is served (#686).
|
||||
// A segment no wider than the request granularity (1 ms — narrower requests are the same request) still
|
||||
// over its budget: a tie cluster, or a burst inside one millisecond. Escalate to the tie budget M before
|
||||
// refusing — up to M rows within one millisecond are served (#686); more are refused, per millisecond
|
||||
// (#691.4 re-review — exact for the millisecond-resolution Gateway).
|
||||
if (segBudget < budget)
|
||||
{
|
||||
segBudget = budget;
|
||||
|
||||
Reference in New Issue
Block a user