docs(historian): #686 open-ended and reverse raw reads on every backend

This commit is contained in:
Joseph Doherty
2026-10-12 14:42:27 -04:00
parent 410c8d8976
commit be2f0cc14d
2 changed files with 30 additions and 0 deletions
+29
View File
@@ -620,6 +620,35 @@ paging time-based:
> anomaly. The ceiling exists to bound server-side memory on pathological data, not to cap normal
> operation.
**Open-ended and reverse raw reads (#669, #686).** With `NumValuesPerNode > 0` one bound may be unset (Part 11): a
start-only read pages **forward** to an open upper bound; an end-only read pages **backward** from `EndTime` — the reverse
window `(MinValue, EndTime]`, newest first, so the first page is the `NumValuesPerNode` values at or before `EndTime`. An
explicit `StartTime > EndTime` is the reverse window `(EndTime, StartTime]`, newest first. These semantics hold on **every**
backend:
- **SqlServer / QuestDB** report `IHistorianDataSource.SupportsReverseRaw = true` and are sent the reverse window directly
(`… > lo AND … <= hi ORDER BY time DESC`).
- **Gateway** (the default backend) reports `false` — the gateway's `ReadRaw` streams ascending only and rejects
`start > end` with `InvalidArgument`. The server never sends it one (and the source refuses one unsent, not counted as a
health failure): it **walks the window back in bounded forward reads** (`ReverseRawReader`). It reads one segment
`(segLo, segHi]` forward — requested `[segLo, segHi + resolution]` and filtered on the samples' own timestamps, which also
drops the rows AVEVA fabricates at a query's start — keeps it newest-first, and steps to the next older segment until the
page is full or the window is exhausted. Segments start at **1 minute** and grow ×4 while sparse, so "the last N values"
of a live tag is normally one small read and sparse history is found in a few dozen tiny ones. Each segment read asks for
at most `MaxTieClusterOverfetch + 1` rows; a read that comes back full may be truncated (its newest rows are the ones
needed), so it is discarded and the segment re-read at a quarter of the width — **never answered from a truncated
read**. A single-timestamp tie cluster larger than `MaxTieClusterOverfetch` cannot be served newest-first this way and
the node is refused **`BadTooManyOperations`** (a request limit, not a backend fault). The walk stops at the **Unix epoch**
(AVEVA holds nothing earlier): a walked-back read returns no sample before 1970. Paging is unchanged — a reverse cursor
resumes as a fresh walk from its resume point.
- **Null** reports `true` (empty in either direction).
> ⚠️ **Client-visible (#686).** Since #669 an end-only capped raw read, and any explicit reverse raw read, on the Gateway
> backend failed the node (`BadHistoryOperationUnsupported`) and was counted as a historian backend failure (the gateway
> refused `start > end`). Before #669 an end-only read on Gateway returned the **oldest** N values ascending. Both now return
> the Part 11 answer — the latest values at or before `EndTime`, newest first. **Events differ:** an end-only events read on
> Gateway still keeps its forward meaning and an explicit reverse events read is refused (see below).
Continuation points are bound to the OPC UA session (the SDK's
`ServerConfiguration.MaxHistoryContinuationPoints` cap, default 100, with oldest-eviction; points
are disposed when the session closes). Resuming an unknown / evicted / released point returns