Skip to main content

differential_dataflow/operators/int_proxy/
join.rs

1//! The proxy join framework.
2//!
3//! A conventional differential join against ordered, copyable proxies.
4//! The backend interprets proxies and is relieved of lattice-time reasoning.
5
6use std::cell::RefCell;
7use std::rc::Rc;
8
9use timely::progress::Timestamp;
10
11use crate::difference::{Multiply, Semigroup};
12use crate::lattice::Lattice;
13use super::{KeyPosition, ProxyBridge};
14use crate::operators::join::{Fresh, JoinTactic};
15use crate::operators::history::ValueHistory;
16
17use super::history::IdHistory;
18
19/// A type that can interpret and retire pairs of lists of batches, joined by keys.
20///
21/// The harness repeatedly invokes [`advance`](Self::advance) to draw a block of the proxy collection,
22/// then [`cross`](Self::cross) to turn that block's matches into output containers, until `advance` reports the key space exhausted.
23pub trait ProxyJoinBackend<T, B0, B1> {
24    /// Independent groups, common to both inputs.
25    type Key: Copy + Ord;
26    /// First input value proxies, valid throughout a block.
27    type V0: Copy + Ord;
28    /// Second input value proxies, valid throughout a block.
29    type V1: Copy + Ord;
30    /// Diff type of the first input.
31    type R0: Semigroup + Multiply<Self::R1, Output = Self::ROut>;
32    /// Diff type of the second input.
33    type R1: Semigroup;
34    /// Diff type of matched records (`R0 * R1`), computed by the harness.
35    type ROut: Semigroup;
36    /// The output container built from matched value ids.
37    type Output;
38
39    /// Populates the two bridges with all updates for all keys that match in a returned range.
40    ///
41    /// The `from` position is `Start` or an inclusive `At(key)` lower bound.
42    /// The backend writes the window's exclusive upper bound as `At(key)`, or `End` when exhausted.
43    /// Each call must strictly advance `from`.
44    /// The returned bridges must contain all updates from both `instance` inputs for keys that are present in both inputs, and
45    /// which are greater or equal to the initial `from`, and not greater or equal to its value when returned.
46    fn advance(
47        &mut self,
48        instance: &JoinInstance<T, B0, B1>,
49        from: &mut KeyPosition<Self::Key>,
50        bridge0: &mut ProxyBridge<T, Self::R0, Self::Key, Self::V0>,
51        bridge1: &mut ProxyBridge<T, Self::R1, Self::Key, Self::V1>,
52    );
53
54    /// Interpret matches derived from the immediately preceding [`Self::advance`] call and place
55    /// them in `output`. The iterator calls `cross` before another `advance`, so a backend may keep
56    /// block-local interpretation state between the two calls. `cross` may be skipped when the
57    /// block produced no matches, in which case the next `advance` may overwrite that state.
58    fn cross(
59        &mut self,
60        instance: &JoinInstance<T, B0, B1>,
61        matches: &mut JoinMatches<T, Self::ROut, Self::Key, Self::V0, Self::V1>,
62        output: &mut Vec<Self::Output>,
63    );
64}
65
66/// A unit of proxied join work, for presentation to the backend.
67pub struct JoinInstance<T, B0, B1> {
68    /// The first input's batches.
69    pub batches0: Vec<B0>,
70    /// The second input's batches.
71    pub batches1: Vec<B1>,
72    /// A lower bound on the meet of pairs of update times.
73    ///
74    /// This can be applied when loading updates to consolidate on load.
75    pub lower: T,
76}
77
78/// Presentation of discovered join matches.
79///
80/// The arrays have common lengths, and are in key order but may not be consolidated.
81pub struct JoinMatches<T, R, K = u64, V0 = u64, V1 = u64> {
82    /// Triples of `(key, (val0, val1))` of matches.
83    pub ids: Vec<(K, (V0, V1))>,
84    /// Times of the updates.
85    pub times: Vec<T>,
86    /// Diffs of the updates.
87    pub diffs: Vec<R>,
88}
89
90impl<T, R, K, V0, V1> Default for JoinMatches<T, R, K, V0, V1> {
91    fn default() -> Self { Self { ids: vec![], times: vec![], diffs: vec![] } }
92}
93
94/// A proxy-space [`JoinTactic`]: matches records of the two drawn runs by `key`.
95/// Its deferred work owns the presentations, so proxy types must be `'static`.
96/// Borrowing proxies from a backend's temporary storage requires a different presentation interface.
97pub struct ProxyJoinTactic<B0, B1, Bk> {
98    backend: Rc<RefCell<Bk>>,
99    _marker: std::marker::PhantomData<(B0, B1)>,
100}
101
102impl<B0, B1, Bk> ProxyJoinTactic<B0, B1, Bk> {
103    /// A join tactic deferring all value semantics to `backend`.
104    pub fn new(backend: Bk) -> Self {
105        ProxyJoinTactic { backend: Rc::new(RefCell::new(backend)), _marker: std::marker::PhantomData }
106    }
107}
108
109impl<T, B0, B1, Bk> JoinTactic<T, B0, B1, Bk::Output> for ProxyJoinTactic<B0, B1, Bk>
110where
111    T: Timestamp + Lattice + 'static,
112    B0: 'static,
113    B1: 'static,
114    Bk: ProxyJoinBackend<T, B0, B1> + 'static,
115    Bk::Output: 'static,
116    Bk::Key: 'static,
117    Bk::V0: 'static,
118    Bk::V1: 'static,
119{
120    fn prep(&mut self, input0: Vec<B0>, input1: Vec<B1>, _fresh: Fresh, meet: T) -> Box<dyn Iterator<Item = Bk::Output>> {
121        Box::new(ProxyJoinIter {
122            backend: Rc::clone(&self.backend),
123            instance: JoinInstance { batches0: input0, batches1: input1, lower: meet },
124            from: KeyPosition::Start,
125            p0: Vec::new(),
126            p1: Vec::new(),
127            h0: IdHistory::new(),
128            h1: IdHistory::new(),
129            matches: JoinMatches::default(),
130            ready: Vec::new(),
131        })
132    }
133}
134
135/// Deferred proxy join computation, as an iterator of output containers.
136///
137/// The iterator draws the proxy collection from the back-end a block at a time (`Bk::advance`).
138/// Each block is then translated to output updates with joined times and multiplied differences,
139/// which are provided to the back-end to translate into output containers, which are then returned.
140struct ProxyJoinIter<T, B0, B1, Bk>
141where
142    Bk: ProxyJoinBackend<T, B0, B1>,
143{
144    /// The backend, shared across all outstanding iterators.
145    backend: Rc<RefCell<Bk>>,
146    /// The iterator's inputs, and the time at which they can consolidate as they load.
147    instance: JoinInstance<T, B0, B1>,
148    /// Progress through the key space, from `Start` through inclusive key bounds to `End`.
149    from: KeyPosition<Bk::Key>,
150    /// The current block: the two runs `advance` last drew, which one `next` consumes entirely.
151    p0: ProxyBridge<T, Bk::R0, Bk::Key, Bk::V0>,
152    p1: ProxyBridge<T, Bk::R1, Bk::Key, Bk::V1>,
153    /// Per-key replay histories, held across the iterator and reloaded per key when needed.
154    h0: IdHistory<T, Bk::R0, Bk::V0>,
155    h1: IdHistory<T, Bk::R1, Bk::V1>,
156    /// The block's matched records, held across blocks to keep their allocations.
157    matches: JoinMatches<T, Bk::ROut, Bk::Key, Bk::V0, Bk::V1>,
158    /// The last block's containers, in reverse, served from the back one `next` at a time.
159    ready: Vec<Bk::Output>,
160}
161
162impl<T, B0, B1, Bk> Iterator for ProxyJoinIter<T, B0, B1, Bk>
163where
164    T: Timestamp + Lattice,
165    Bk: ProxyJoinBackend<T, B0, B1>,
166{
167    type Item = Bk::Output;
168
169    /// Serve a ready container, else draw and cross blocks until one yields any.
170    fn next(&mut self) -> Option<Bk::Output> {
171        while self.ready.is_empty() && self.from != KeyPosition::End {
172            self.refill();
173            self.work();
174            if !self.matches.ids.is_empty() { self.cross(); }
175        }
176        self.ready.pop()
177    }
178}
179
180impl<T, B0, B1, Bk> ProxyJoinIter<T, B0, B1, Bk>
181where
182    T: Timestamp + Lattice,
183    Bk: ProxyJoinBackend<T, B0, B1>,
184{
185    /// Draw the next block from the backend.
186    fn refill(&mut self) {
187        self.p0.clear();
188        self.p1.clear();
189        let before = self.from;
190        self.backend.borrow_mut().advance(&self.instance, &mut self.from, &mut self.p0, &mut self.p1);
191        // Without progress the iterator would never retire, so this guards liveness as well as contract.
192        debug_assert!(
193            self.from > before,
194            "advance must either strictly increase `from` or report the iteration complete",
195        );
196        super::debug_assert_sorted_bridge(&self.p0, "advance (bridge0)");
197        super::debug_assert_sorted_bridge(&self.p1, "advance (bridge1)");
198        // A key outside `[before, from)` belongs to an earlier or later block.
199        // Both cases split a key across blocks, silently dropping matches that would cross the split.
200        debug_assert!(
201            {
202                let mut keys = self.p0.iter().map(|r| r.0.0).chain(self.p1.iter().map(|r| r.0.0));
203                keys.all(|k| before <= KeyPosition::At(k) && KeyPosition::At(k) < self.from)
204            },
205            "advance must report a key entirely within the block that first mentions it",
206        );
207    }
208
209    /// Match the whole of the current block into the match buffers.
210    fn work(&mut self) {
211        // Disjoint field borrows, as `join_key` holds the bridges and the buffers at once.
212        let (p0, p1) = (&self.p0, &self.p1);
213        let (h0, h1) = (&mut self.h0, &mut self.h1);
214
215        let (mut i, mut j) = (0usize, 0usize);
216        while i < p0.len() && j < p1.len() {
217            let ki = p0[i].0.0;
218            debug_assert!(ki == p1[j].0.0, "advance must report common keys");
219            let mut e0 = i;
220            while e0 < p0.len() && p0[e0].0.0 == ki { e0 += 1; }
221            let mut e1 = j;
222            while e1 < p1.len() && p1[e1].0.0 == ki { e1 += 1; }
223            join_key(ki, p0, i..e0, p1, j..e1, h0, h1, &mut self.matches);
224            i = e0;
225            j = e1;
226        }
227        debug_assert!(i == p0.len() && j == p1.len(), "both bridges must drain together");
228    }
229
230    /// Turn the block's matches into containers, ready to be served one at a time.
231    fn cross(&mut self) {
232        self.backend.borrow_mut().cross(
233            &self.instance,
234            &mut self.matches,
235            &mut self.ready,
236        );
237        // `next` serves from the back, so reverse to ship in the order the backend produced.
238        self.ready.reverse();
239        self.matches.ids.clear();
240        self.matches.times.clear();
241        self.matches.diffs.clear();
242    }
243}
244
245/// Match one key's records across the two presented runs.
246///
247/// If either history is small, this performs a direct cross product.
248/// If both histories are large, this replays the histories compacting as it goes in
249/// order to (potentially) avoid quadratic blow-up.
250fn join_key<T, R0, R1, RO, K: Copy + Ord, V0: Copy + Ord, V1: Copy + Ord>(
251    kh: K,
252    p0: &ProxyBridge<T, R0, K, V0>,
253    r0: std::ops::Range<usize>,
254    p1: &ProxyBridge<T, R1, K, V1>,
255    r1: std::ops::Range<usize>,
256    h0: &mut IdHistory<T, R0, V0>,
257    h1: &mut IdHistory<T, R1, V1>,
258    matches: &mut JoinMatches<T, RO, K, V0, V1>,
259) where
260    T: Lattice + Timestamp,
261    R0: Semigroup + Multiply<R1, Output = RO> + Clone,
262    R1: Semigroup + Clone,
263{
264    if r0.len() < 16 || r1.len() < 16 {
265        for a in r0 {
266            for b in r1.clone() {
267                matches.ids.push((kh, (p0[a].0.1, p1[b].0.1)));
268                let mut time = p0[a].1.clone();
269                time.join_assign(&p1[b].1);
270                matches.times.push(time);
271                matches.diffs.push(p0[a].2.clone().multiply(&p1[b].2));
272            }
273        }
274    }
275    else {
276        h0.load_iter(r0.map(|i| (p0[i].0.1, p0[i].1.clone(), p0[i].2.clone())), None);
277        h1.load_iter(r1.map(|i| (p1[i].0.1, p1[i].1.clone(), p1[i].2.clone())), None);
278        bilinear_wave(h0, h1, |v0, v1, t, d| {
279            matches.ids.push((kh, (v0, v1)));
280            matches.times.push(t);
281            matches.diffs.push(d);
282        });
283    }
284}
285
286/// Produces the join of two histories: every pair of edits, diffs multiplied and times
287/// joined, visited in time order. Repeatedly steps the history with the earlier un-replayed
288/// edit and multiplies it against the other's buffer, which is consolidated under the meet of
289/// its remaining times as the wave advances — so work is bounded by the netted accumulation
290/// sizes rather than the raw history lengths.
291///
292/// `emit` receives every produced `(id0, id1, joined time, multiplied diff)`. Both histories
293/// must be pre-loaded (`load`/`load_iter`) and are fully drained. For small histories a plain
294/// cross product is cheaper; callers should gate on size.
295fn bilinear_wave<V0, V1, T, R0, R1, RO>(
296    h0: &mut ValueHistory<V0, T, R0>,
297    h1: &mut ValueHistory<V1, T, R1>,
298    mut emit: impl FnMut(V0, V1, T, RO),
299) where
300    V0: Copy + Ord,
301    V1: Copy + Ord,
302    T: Ord + Clone + Lattice,
303    R0: Semigroup + Multiply<R1, Output = RO> + Clone,
304    R1: Semigroup + Clone,
305{
306    while h0.time().is_some() && h1.time().is_some() {
307        if h0.time().unwrap() < h1.time().unwrap() {
308            h1.advance_buffer_by(h0.meet().unwrap());
309            let (v0, t0, d0) = h0.edit().unwrap();
310            for ((v1, t1), d1) in h1.buffer() {
311                let mut time = t0.clone();
312                time.join_assign(t1);
313                emit(v0, *v1, time, d0.clone().multiply(d1));
314            }
315            h0.step();
316        } else {
317            h0.advance_buffer_by(h1.meet().unwrap());
318            let (v1, t1, d1) = h1.edit().unwrap();
319            for ((v0, t0), d0) in h0.buffer() {
320                let mut time = t0.clone();
321                time.join_assign(t1);
322                emit(*v0, v1, time, d0.clone().multiply(d1));
323            }
324            h1.step();
325        }
326    }
327    while h0.time().is_some() {
328        h1.advance_buffer_by(h0.meet().unwrap());
329        let (v0, t0, d0) = h0.edit().unwrap();
330        for ((v1, t1), d1) in h1.buffer() {
331            let mut time = t0.clone();
332            time.join_assign(t1);
333            emit(v0, *v1, time, d0.clone().multiply(d1));
334        }
335        h0.step();
336    }
337    while h1.time().is_some() {
338        h0.advance_buffer_by(h1.meet().unwrap());
339        let (v1, t1, d1) = h1.edit().unwrap();
340        for ((v0, t0), d0) in h0.buffer() {
341            let mut time = t0.clone();
342            time.join_assign(t1);
343            emit(*v0, v1, time, d0.clone().multiply(d1));
344        }
345        h1.step();
346    }
347}