Expand description
A backend that reads its inputs as of a frontier.
A trace’s accumulations are correct only at or beyond its compaction frontier: a batch may hold
a retraction advanced to the frontier beside the insertion it retracts, so an earlier time can
show a count no time had. A reader that starts after the trace has compacted must read every
update’s time advanced to a frontier since, as TraceFrontier
does for a cursor. The proxy tactics hand batches to a backend, which reads their times as
stored, so BackendFrontier reads what its inner backend draws as of since.
Advancing is not injective: updates that coincide once advanced are consolidated, since the tactics require consolidated bridges, and a join’s bridges must then still hold keys common to both inputs. Both are done only where a time actually moved.
§What since must be
since must be beyond every frontier the trace has compacted by: each of its elements at or
beyond some element of each such frontier. Then advancing a stored time, already advanced by a
compaction frontier F, by since is advancing the original time by since: with
x = advance_F(t), t ≤ x ≤ t ∨ f for some f ≤ s for each s in since, so
t ∨ s ≤ x ∨ s ≤ t ∨ f ∨ s = t ∨ s, and the meets over s agree. So merged and unmerged
copies of one update land on one time, in any lattice. A reader checks this against the
trace’s logical compaction frontier when it imports, as TraceAgent::import_frontier does by
taking that frontier as since. In particular, reading as of the meet of the trace’s
compaction frontier is not safe in a non-distributive lattice: it advances too little, and
leaves times between the meet and the frontier where the trace promises nothing.
§Where the work is, and what is exact
Everything below uses only the defining property of Lattice::advance_by: for every t'
beyond a frontier F, t ≤ t' ⟺ advance_F(t) ≤ t'; together with t ≤ advance_F(t), that
advance_F(t) = t when t is beyond F, and the squeeze t ∨ t' = advance_F(t) ∨ t' for
t' beyond F. None of it needs the lattice to be distributive.
Each unit of work carries a bound on the times it is about: the join’s meet of the fresh
input’s times (JoinInstance::lower), the reduce’s retire interval (ReduceInstance::lower).
When that bound is beyond since, the wrapper changes nothing, for any inner backend:
- A join’s output times are
t0 ∨ t1with the fresh input’s time beyondsince, so by the squeeze advancing either input bysinceleaves every output time as it is: the output is identical to the as-of reading’s. - A reduce evaluates only at times
atat or beyond its lower bound, hence beyondsince. Whether a record is in the accumulation there (t ≤ at) and which times it synthesizes (t ∨ at) are the same fortandadvance(t), so the retire’s output is identical.
That is the steady state of an operator reading a trace it imported: every unit after the first has its bound beyond the import time. The first unit reads the trace’s history, and only it pays for the read. There the wrapper advances what the inner backend presents, and:
- It is exact when the inner backend presents stored times, or presents them advanced by a
bound that
sinceis beyond (every first unit, whose bound is the minimum): the same squeeze as for compaction. It is also exact whensincehas one element, whatever the backend does. - In general the inner backend may consolidate on load by its bound
lower, and in a non-distributive latticeadvance_since(t ∨ lower)need not equaladvance_since(t)whensinceandlowercross. Then the output agrees with the as-of reading at every time beyondsince, which is all a trace read as ofsincepromises its readers anyway: fort'beyondsince,advance(t ∨ c) ≤ t' ⟺ t ≤ t' ∧ c ≤ t', andc ≤ lower ≤the fresh time≤ t'.
§The join reads both inputs
Reading one input of a join as of since and reading both agree at every time beyond since,
and are identical when the other input’s times are beyond since (the squeeze again). So the
join wrapper reads both inputs as of since; a join whose inputs are read as of two frontiers
nests two wrappers.
Structs§
- Backend
Frontier - A backend whose inputs are read as of
since: every time the inner backend draws is advanced by it, and what then coincides is consolidated.
Functions§
- read_
as_ of - Read
bridgeas ofsince: times advanced by it, and the bridge consolidated if any moved, as advancing can make records coincide. Returns whether any did; if none, the bridge is as presented, still sorted and consolidated.