fix(historian): #691.5 a reverse walk proves the rest empty once and carries it page to page
The last pages of a deep reverse page-through on a forward-only backend each walked down to 1970 one x4 growth step at a time (~14-16 reads, ~2.5 s live). After two empty segments the walk now asks once per page for the whole remainder at the page budget (a truncated answer is discarded and the walk carries on), and the stretch the page's last complete read proved empty rides the continuation point (HistoryContinuationState.EmptyFromTicks/EmptyToTicks, session-held, so no versioning) so the next page steps over it. Fuzz now pages with the walk's own hints and with true random empty stretches.
This commit is contained in:
@@ -6184,7 +6184,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
{
|
||||
DateTime startUtc;
|
||||
var boundarySkip = 0;
|
||||
long segmentWidth = 0; // #686: the walk-back's width, carried page to page
|
||||
var walkHint = default(ReverseWalkHint); // #686/#689/#691: the walk-back's width + proven-empty stretch, page to page
|
||||
|
||||
string tagname;
|
||||
if (inboundCp is { Length: > 0 })
|
||||
@@ -6205,7 +6205,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
boundarySkip = state.BoundarySkipCount;
|
||||
endUtc = state.EndUtc;
|
||||
numValuesPerNode = state.NumValuesPerNode;
|
||||
segmentWidth = state.SegmentWidthTicks;
|
||||
walkHint = state.WalkHint;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -6243,9 +6243,9 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
{
|
||||
var page = await ReverseRawReader.ReadAsync(
|
||||
source, tagname, startUtc, endUtc, numValuesPerNode, MaxTieClusterOverfetch,
|
||||
Clock.GetUtcNow().UtcDateTime, segmentWidth, ct)
|
||||
Clock.GetUtcNow().UtcDateTime, walkHint, ct)
|
||||
.ConfigureAwait(false);
|
||||
segmentWidth = page.WidthHintTicks;
|
||||
walkHint = page.Hint;
|
||||
sourceResult = new HistorianRead(page.Samples, ContinuationPoint: null);
|
||||
}
|
||||
else
|
||||
@@ -6324,7 +6324,8 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
if (nextStartUtc is { } resumeAt)
|
||||
{
|
||||
var clusterState = new HistoryContinuationState(
|
||||
tagname, resumeAt, endUtc, nextSkip, numValuesPerNode, SegmentWidthTicks: segmentWidth);
|
||||
tagname, resumeAt, endUtc, nextSkip, numValuesPerNode, SegmentWidthTicks: walkHint.WidthTicks,
|
||||
EmptyFromTicks: walkHint.EmptyFromTicks, EmptyToTicks: walkHint.EmptyToTicks);
|
||||
clusterCp = _historyContinuationStore.Save(session, clusterState);
|
||||
}
|
||||
|
||||
@@ -6347,7 +6348,8 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
{
|
||||
HistoryPaging.ComputeResumeCursor(samples, out var nextStart, out var skip);
|
||||
var nextState = new HistoryContinuationState(
|
||||
tagname, nextStart, endUtc, skip, numValuesPerNode, SegmentWidthTicks: segmentWidth);
|
||||
tagname, nextStart, endUtc, skip, numValuesPerNode, SegmentWidthTicks: walkHint.WidthTicks,
|
||||
EmptyFromTicks: walkHint.EmptyFromTicks, EmptyToTicks: walkHint.EmptyToTicks);
|
||||
// Save may return null (no session on this request) ⇒ degrade to single-shot for this node.
|
||||
// Built AFTER historyData so a failure projecting samples can never orphan a stored cursor.
|
||||
outboundCp = _historyContinuationStore.Save(session, nextState);
|
||||
|
||||
Reference in New Issue
Block a user