fix(historian): #692 every HistoryRead-Raw page is full — no empty page between values
A resumed raw page asked the backend for only N rows from the boundary timestamp (inclusive), then trimmed the boundary ties already emitted: it netted N - skip values, and at N = 1 nothing — the empty "full" page was taken for a stalled tie cluster and a MaxTieClusterOverfetch + 1 (65,537-row) over-fetch stepped one tick, returning an EMPTY page with a continuation point between every value (8 values = 17 pages / 25 reads), forward and reverse, with or without ReturnBounds, on every backend. Each page's read now asks for N + boundarySkip + 1 rows: the ties it trims, the page and one lookahead row. The lookahead row is the 'more data' signal, so an exact multiple ends on its last full page (no trailing empty page). The over-fetch fires only when every row past the trimmed ties is still on the boundary (a genuine cluster larger than the page); a cursor left inside a cluster is flagged WithinTieCluster and over-fetches directly. A page that never leaves the boundary adds the prior skip to its cursor. Low items: BoundedReadHasAnythingAsync carries its walk hint and, when there is no data, the end bound it found (HistoryEndBound.Resolved + DataExhausted), so the next page reads nothing; HistoryBounds.IsPlaceholder is an explicit marker (ConditionalWeakTable over the NotFound instances) instead of status+null shape, so a stored Bad_BoundNotFound row is data. Tests: NodeManagerHistoryReadPageFillTests (N=1 every shape/direction/bounds, tie clusters straddling boundaries at N=1/2/3, exact-multiple, carried end bound + walk hint, stored placeholder-shaped row). Existing paging tests updated where they encoded the short pages.
This commit is contained in:
@@ -66,6 +66,11 @@ namespace ZB.MOM.WW.OtOpcUa.OpcUaServer;
|
||||
/// <see cref="HistoryCursorKind.Raw"/> with <c>ReturnBounds = true</c> only (Gitea #691): the end bound still owed —
|
||||
/// Part 11 returns it with the last page. <c>null</c> = bounds not requested.
|
||||
/// </param>
|
||||
/// <param name="WithinTieCluster">
|
||||
/// <see cref="HistoryCursorKind.Raw"/> only (Gitea #692): the previous page was a slice of an oversized tie cluster at
|
||||
/// <paramref name="NextStartUtc"/> that still has un-emitted ties — the next page over-fetches the cluster directly
|
||||
/// instead of first re-reading <c>N + skip + 1</c> rows that are known to be ties.
|
||||
/// </param>
|
||||
internal sealed record HistoryContinuationState(
|
||||
string Tagname,
|
||||
DateTime NextStartUtc,
|
||||
@@ -79,7 +84,8 @@ internal sealed record HistoryContinuationState(
|
||||
long SegmentWidthTicks = 0,
|
||||
long EmptyFromTicks = 0,
|
||||
long EmptyToTicks = 0,
|
||||
HistoryEndBound? PendingEndBound = null)
|
||||
HistoryEndBound? PendingEndBound = null,
|
||||
bool WithinTieCluster = false)
|
||||
{
|
||||
/// <summary>The walk-back hint this cursor carries (#686/#689/#691).</summary>
|
||||
public ReverseWalkHint WalkHint => new(SegmentWidthTicks, EmptyFromTicks, EmptyToTicks);
|
||||
|
||||
Reference in New Issue
Block a user