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:
Joseph Doherty
2026-10-12 16:32:00 -04:00
parent d33992034d
commit 186b7d5ea5
7 changed files with 222 additions and 29 deletions
+9 -1
View File
@@ -659,11 +659,19 @@ backend:
rows of its burst over and over). Only **one timestamp holding more than `M` samples** is refused,
**`BadTooManyOperations`** (a request limit, not a backend fault); a cluster of exactly `M`, or neighbouring
clusters each within `M`, is served.
- **Proving the rest empty (#691).** Walking down through empty history to the floor used to cost one read per ×4
growth step — ~14–16 reads (~2.5 s live) on **every** last page of a deep page-through. Now, after two consecutive
empty segments, the walk asks **once per page** for the whole remainder down to the floor at the page budget: a
complete answer ends the walk in one read; a truncated one (deep history) is discarded and the walk carries on. And
the stretch the page's last complete read proved empty below its oldest row (often all the way to the floor) rides
the continuation point, so the next page steps straight over it. Data backfilled into such a stretch between pages
is not seen by that read — paging was never a snapshot (the forward path misses data inserted behind its cursor too).
- **Bounds.** The walk starts no later than **now + 1 day** (a `StartTime = DateTime.MaxValue` read does not send
year-9999 query starts) and never reads before the **Unix epoch** (AVEVA holds nothing earlier): a walked-back read
returns only samples strictly after 1970-01-01T00:00:00Z and no later than now + 1 day.
Paging is unchanged — a reverse cursor resumes as a fresh walk from its resume point.
Paging is otherwise unchanged — a reverse cursor resumes as a fresh walk from its resume point, carrying the width and
the proven-empty stretch (session-held cursor state, never serialized, so the new fields need no versioning).
- **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