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:
Joseph Doherty
2026-10-12 17:50:39 -04:00
parent 574c3aa1b0
commit 9bb1363cd6
8 changed files with 109 additions and 20 deletions
@@ -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)