Skip to main content

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}