differential_dataflow/operators/int_proxy/frontier.rs
1//! A backend that reads its inputs as of a frontier.
2//!
3//! A trace's accumulations are correct only at or beyond its compaction frontier: a batch may hold
4//! a retraction advanced to the frontier beside the insertion it retracts, so an earlier time can
5//! show a count no time had. A reader that starts after the trace has compacted must read every
6//! update's time advanced to a frontier `since`, as [`TraceFrontier`](crate::trace::wrappers::frontier::TraceFrontier)
7//! does for a cursor. The proxy tactics hand batches to a backend, which reads their times as
8//! stored, so [`BackendFrontier`] reads what its inner backend draws as of `since`.
9//!
10//! Advancing is not injective: updates that coincide once advanced are consolidated, since the
11//! tactics require consolidated bridges, and a join's bridges must then still hold keys common to
12//! both inputs. Both are done only where a time actually moved.
13//!
14//! # What `since` must be
15//!
16//! `since` must be beyond every frontier the trace has compacted by: each of its elements at or
17//! beyond some element of each such frontier. Then advancing a stored time, already advanced by a
18//! compaction frontier `F`, by `since` is advancing the original time by `since`: with
19//! `x = advance_F(t)`, `t ≤ x ≤ t ∨ f` for some `f ≤ s` for each `s` in `since`, so
20//! `t ∨ s ≤ x ∨ s ≤ t ∨ f ∨ s = t ∨ s`, and the meets over `s` agree. So merged and unmerged
21//! copies of one update land on one time, in any lattice. A reader checks this against the
22//! trace's logical compaction frontier when it imports, as `TraceAgent::import_frontier` does by
23//! taking that frontier as `since`. In particular, reading as of the *meet* of the trace's
24//! compaction frontier is not safe in a non-distributive lattice: it advances too little, and
25//! leaves times between the meet and the frontier where the trace promises nothing.
26//!
27//! # Where the work is, and what is exact
28//!
29//! Everything below uses only the defining property of [`Lattice::advance_by`]: for every `t'`
30//! beyond a frontier `F`, `t ≤ t' ⟺ advance_F(t) ≤ t'`; together with `t ≤ advance_F(t)`, that
31//! `advance_F(t) = t` when `t` is beyond `F`, and the squeeze `t ∨ t' = advance_F(t) ∨ t'` for
32//! `t'` beyond `F`. None of it needs the lattice to be distributive.
33//!
34//! Each unit of work carries a bound on the times it is about: the join's meet of the fresh
35//! input's times (`JoinInstance::lower`), the reduce's retire interval (`ReduceInstance::lower`).
36//! When that bound is beyond `since`, the wrapper changes nothing, for any inner backend:
37//!
38//! - A join's output times are `t0 ∨ t1` with the fresh input's time beyond `since`, so by the
39//! squeeze advancing either input by `since` leaves every output time as it is: the output is
40//! identical to the as-of reading's.
41//! - A reduce evaluates only at times `at` at or beyond its lower bound, hence beyond `since`.
42//! Whether a record is in the accumulation there (`t ≤ at`) and which times it synthesizes
43//! (`t ∨ at`) are the same for `t` and `advance(t)`, so the retire's output is identical.
44//!
45//! That is the steady state of an operator reading a trace it imported: every unit after the
46//! first has its bound beyond the import time. The first unit reads the trace's history, and only
47//! it pays for the read. There the wrapper advances what the inner backend presents, and:
48//!
49//! - **It is exact** when the inner backend presents stored times, or presents them advanced by a
50//! bound that `since` is beyond (every first unit, whose bound is the minimum): the same squeeze
51//! as for compaction. It is also exact when `since` has one element, whatever the backend does.
52//! - **In general** the inner backend may consolidate on load by its bound `lower`, and in a
53//! non-distributive lattice `advance_since(t ∨ lower)` need not equal `advance_since(t)` when
54//! `since` and `lower` cross. Then the output agrees with the as-of reading at every time beyond
55//! `since`, which is all a trace read as of `since` promises its readers anyway: for `t'` beyond
56//! `since`, `advance(t ∨ c) ≤ t' ⟺ t ≤ t' ∧ c ≤ t'`, and `c ≤ lower ≤` the fresh time `≤ t'`.
57//!
58//! # The join reads both inputs
59//!
60//! Reading one input of a join as of `since` and reading both agree at every time beyond `since`,
61//! and are identical when the other input's times are beyond `since` (the squeeze again). So the
62//! join wrapper reads both inputs as of `since`; a join whose inputs are read as of two frontiers
63//! nests two wrappers.
64
65use timely::progress::{Antichain, Timestamp};
66
67use crate::consolidation::consolidate_updates;
68use crate::difference::Semigroup;
69use crate::lattice::Lattice;
70use crate::trace::Description;
71
72use super::join::{JoinInstance, JoinMatches, ProxyJoinBackend};
73use super::reduce::{ProxyReduceBackend, ReduceInstance, ReduceWindow};
74use super::{KeyPosition, ProxyBridge};
75
76/// A backend whose inputs are read as of `since`: every time the inner backend draws is advanced
77/// by it, and what then coincides is consolidated.
78///
79/// Only reads are translated. Outputs are the inner backend's, at the operator's times, which are
80/// beyond `since` once its inputs are. An empty `since` reads the inputs as they are.
81pub struct BackendFrontier<B, T> {
82 inner: B,
83 since: Antichain<T>,
84}
85
86impl<B, T> BackendFrontier<B, T> {
87 /// Read `inner`'s inputs as of `since`, which must be beyond every frontier the inputs' traces
88 /// have compacted by (see the module documentation).
89 pub fn new(inner: B, since: Antichain<T>) -> Self { BackendFrontier { inner, since } }
90 /// The frontier the inputs are read as of.
91 pub fn since(&self) -> &Antichain<T> { &self.since }
92 /// The wrapped backend.
93 pub fn inner(&self) -> &B { &self.inner }
94 /// The wrapped backend, mutably: a wrapper implementing a backend trait defined outside this
95 /// crate, such as dogs3's half-join backend, drives the inner backend through this.
96 pub fn inner_mut(&mut self) -> &mut B { &mut self.inner }
97}
98
99/// Read `bridge` as of `since`: times advanced by it, and the bridge consolidated if any moved,
100/// as advancing can make records coincide. Returns whether any did; if none, the bridge is as
101/// presented, still sorted and consolidated.
102///
103/// Public so that a wrapper for a backend trait defined outside this crate reads its bridges the
104/// one way: dogs3's half-join wrapper reads the arranged side with it.
105pub fn read_as_of<T, R, K, V>(bridge: &mut ProxyBridge<T, R, K, V>, since: &Antichain<T>) -> bool
106where
107 T: Timestamp + Lattice,
108 R: Semigroup,
109 K: Ord,
110 V: Ord,
111{
112 let mut moved = false;
113 for (_, time, _) in bridge.iter_mut() {
114 // A time beyond `since` is its own advance; the test is cheaper than the advance.
115 if !since.less_equal(time) {
116 let stored = time.clone();
117 time.advance_by(since.borrow());
118 moved |= *time != stored;
119 }
120 }
121 if moved { consolidate_updates(bridge); }
122 moved
123}
124
125/// Keep only `bridge`'s keys that `other` holds. Both are in key order.
126fn common_keys<T, R, K: Copy + Ord, V, T2, R2, V2>(bridge: &mut ProxyBridge<T, R, K, V>, other: &ProxyBridge<T2, R2, K, V2>) {
127 let mut at = 0;
128 bridge.retain(|((key, _), _, _)| {
129 while at < other.len() && other[at].0.0 < *key { at += 1; }
130 at < other.len() && other[at].0.0 == *key
131 });
132}
133
134// ===== Join =====
135
136impl<T, B0, B1, B> ProxyJoinBackend<T, B0, B1> for BackendFrontier<B, T>
137where
138 T: Timestamp + Lattice,
139 B: ProxyJoinBackend<T, B0, B1>,
140{
141 type Key = B::Key;
142 type V0 = B::V0;
143 type V1 = B::V1;
144 type R0 = B::R0;
145 type R1 = B::R1;
146 type ROut = B::ROut;
147 type Output = B::Output;
148
149 fn advance(
150 &mut self,
151 instance: &JoinInstance<T, B0, B1>,
152 from: &mut KeyPosition<Self::Key>,
153 bridge0: &mut ProxyBridge<T, Self::R0, Self::Key, Self::V0>,
154 bridge1: &mut ProxyBridge<T, Self::R1, Self::Key, Self::V1>,
155 ) {
156 self.inner.advance(instance, from, bridge0, bridge1);
157 // Every output time is at or beyond the fresh input's times, hence `lower`: beyond `since`.
158 if self.since.is_empty() || self.since.less_equal(&instance.lower) { return; }
159 // A key whose updates net away on one side has nothing to match, and the harness requires
160 // the bridges' keys to agree.
161 let moved0 = read_as_of(bridge0, &self.since);
162 let moved1 = read_as_of(bridge1, &self.since);
163 if moved0 || moved1 {
164 common_keys(bridge1, bridge0);
165 common_keys(bridge0, bridge1);
166 }
167 }
168
169 fn cross(
170 &mut self,
171 instance: &JoinInstance<T, B0, B1>,
172 matches: &mut JoinMatches<T, Self::ROut, Self::Key, Self::V0, Self::V1>,
173 output: &mut Vec<Self::Output>,
174 ) {
175 self.inner.cross(instance, matches, output)
176 }
177}
178
179// ===== Reduce =====
180
181impl<T, B1, B2, B> ProxyReduceBackend<T, B1, B2> for BackendFrontier<B, T>
182where
183 T: Timestamp + Lattice,
184 B: ProxyReduceBackend<T, B1, B2>,
185{
186 type Key = B::Key;
187 type VIn = B::VIn;
188 type VOut = B::VOut;
189 type RIn = B::RIn;
190 type ROut = B::ROut;
191
192 fn begin(&mut self, description: Description<T>) { self.inner.begin(description) }
193
194 fn next_window(
195 &mut self,
196 instance: &ReduceInstance<'_, T, B1, B2>,
197 changed: &[Self::Key],
198 from: &mut KeyPosition<Self::Key>,
199 window: &mut ReduceWindow<T, Self::RIn, Self::ROut, Self::Key, Self::VIn, Self::VOut>,
200 ) {
201 self.inner.next_window(instance, changed, from, window);
202 // The retire evaluates only at times at or beyond `lower`; beyond `since`, a record and
203 // its advanced self accumulate and synthesize alike.
204 if self.since.is_empty() || instance.lower.iter().all(|time| self.since.less_equal(time)) { return; }
205 // The input, novel and prior merged, is read as of `since`; so is the novel time support,
206 // which seeds the retire's interesting times. The output is the operator's own, at times
207 // beyond `since` already.
208 read_as_of(&mut window.input, &self.since);
209 let mut moved = false;
210 for (_, time) in window.seeds.iter_mut() {
211 if !self.since.less_equal(time) {
212 let stored = time.clone();
213 time.advance_by(self.since.borrow());
214 moved |= *time != stored;
215 }
216 }
217 if moved {
218 window.seeds.sort();
219 window.seeds.dedup();
220 }
221 }
222
223 fn reduce_corrections(
224 &mut self,
225 keys: &[Self::Key],
226 in_ends: &[usize],
227 input: &[(Self::VIn, Self::RIn)],
228 out_ends: &[usize],
229 output: &[(Self::VOut, Self::ROut)],
230 ) -> (Vec<(Self::VOut, Self::ROut)>, Vec<usize>) {
231 self.inner.reduce_corrections(keys, in_ends, input, out_ends, output)
232 }
233
234 fn emit(&mut self, records: &[((Self::Key, Self::VOut), T, Self::ROut)]) { self.inner.emit(records) }
235
236 fn finish(&mut self) -> Option<B2> { self.inner.finish() }
237}