From 2d11f8bb0559ead1cee6e9b36f7091c75a584164 Mon Sep 17 00:00:00 2001 From: Frank McSherry Date: Wed, 19 Aug 2026 16:35:03 -0400 Subject: [PATCH 1/4] Preliminary HalfJoin tactic --- dogsdogsdogs/examples/delta_query2.rs | 4 +- dogsdogsdogs/src/operators/count.rs | 14 +- dogsdogsdogs/src/operators/half_join.rs | 606 ++++++++++++++++-------- dogsdogsdogs/src/operators/propose.rs | 14 +- dogsdogsdogs/src/operators/validate.rs | 14 +- 5 files changed, 419 insertions(+), 233 deletions(-) diff --git a/dogsdogsdogs/examples/delta_query2.rs b/dogsdogsdogs/examples/delta_query2.rs index f29a95a1a..b9c53d0a8 100644 --- a/dogsdogsdogs/examples/delta_query2.rs +++ b/dogsdogsdogs/examples/delta_query2.rs @@ -43,7 +43,7 @@ fn main() { changes1, forward2, closure, - |t1,t2| t1.lt(t2), // This one ignores concurrent updates. + true, // This one ignores concurrent updates. |key, val1, val2| (key.clone(), (val1.clone(), val2.clone())), ); @@ -52,7 +52,7 @@ fn main() { changes2, forward1, closure, - |t1,t2| t1.le(t2), // This one can "see" concurrent updates. + false, // This one can "see" concurrent updates. |key, val1, val2| (key.clone(), (val2.clone(), val1.clone())), ); diff --git a/dogsdogsdogs/src/operators/count.rs b/dogsdogsdogs/src/operators/count.rs index 8aa0eebc7..a40c0155a 100644 --- a/dogsdogsdogs/src/operators/count.rs +++ b/dogsdogsdogs/src/operators/count.rs @@ -64,15 +64,9 @@ where }; use crate::operators::half_join::half_join_internal_unsafe as half_join_unsafe; - // Branch once here, so that each comparison monomorphizes rather than testing `strict` at - // every timestamp. The cost is instantiating `half_join` twice. - if strict { - half_join_unsafe::<_, _, _, _, _, _, _, _, Output>( - requests, arrangement, frontier_func, |t1, t2| t1 < t2, |_timer, _count| false, output_func) - } - else { - half_join_unsafe::<_, _, _, _, _, _, _, _, Output>( - requests, arrangement, frontier_func, |t1, t2| t1 <= t2, |_timer, _count| false, output_func) - } + // `strict` now reaches the join as a value rather than as a comparison closure, so there is + // nothing left to monomorphize by branching here; the test is made per arrangement time. + half_join_unsafe::<_, _, _, _, _, _, _, Output>( + requests, arrangement, frontier_func, strict, |_timer, _count| false, output_func) .as_collection() } diff --git a/dogsdogsdogs/src/operators/half_join.rs b/dogsdogsdogs/src/operators/half_join.rs index 102a0f108..908cfff1d 100644 --- a/dogsdogsdogs/src/operators/half_join.rs +++ b/dogsdogsdogs/src/operators/half_join.rs @@ -1,8 +1,8 @@ /// Streaming asymmetric join between updates (K,V1) and an arrangement on (K,V2). /// /// The asymmetry is that the join only responds to streamed updates, not to changes in the arrangement. -/// Streamed updates join only with matching arranged updates at lesser times *in the total order*, and -/// subject to a predicate supplied by the user (roughly: strictly less, or not). +/// Streamed updates join only with matching arranged updates at lesser times *in the total order*, +/// where a `strict` flag says whether an equal time counts as lesser. /// /// This behavior can ensure that any pair of matching updates interact exactly once. /// @@ -20,20 +20,22 @@ use std::collections::VecDeque; use std::ops::Mul; -use timely::ContainerBuilder; -use timely::container::CapacityContainerBuilder; +use timely::{Container, ContainerBuilder}; +use timely::container::{CapacityContainerBuilder, DrainContainer, NoopBuilder, PushInto, SizableContainer}; use timely::dataflow::Stream; -use timely::dataflow::channels::pact::{Pipeline, Exchange}; +use timely::dataflow::channels::ContainerBytes; +use timely::dataflow::channels::pact::{Pipeline, ExchangeCore}; use timely::dataflow::operators::Operator; +use timely::dataflow::operators::generic::OutputBuilderSession; use timely::PartialOrder; -use timely::progress::{Antichain, ChangeBatch, Timestamp}; +use timely::progress::{Antichain, Timestamp}; use timely::progress::frontier::MutableAntichain; use differential_dataflow::{ExchangeData, VecCollection, AsCollection, Hashable}; -use differential_dataflow::difference::{Monoid, Semigroup}; +use differential_dataflow::difference::Semigroup; use differential_dataflow::lattice::Lattice; use differential_dataflow::operators::arrange::Arranged; -use differential_dataflow::trace::{BatchCursor, BatchDiff, BatchTimeGat, BatchVal, Cursor, Navigable, TraceReader}; +use differential_dataflow::trace::{BatchReader, BatchCursor, BatchDiff, BatchVal, Cursor, Navigable, TraceReader}; use differential_dataflow::trace::cursor::cursor_list; use differential_dataflow::consolidation::{consolidate, consolidate_updates}; use differential_dataflow::trace::implementations::BatchContainer; @@ -59,26 +61,29 @@ use timely::dataflow::operators::CapabilitySet; /// This last constraint is important to ensure that we correctly produce /// all pairs of output updates across multiple `half_join` operators. /// +/// The `strict` argument selects the comparison: `true` requires `time2` to be strictly less than +/// `initial_time`, and `false` also admits `time2` equal to it. A delta query pairs a strict operator +/// with a non-strict one, which is what makes each pair of matching updates interact exactly once. +/// /// Notice that the time is hoisted up into data. The expectation is that /// once out of the "delta flow region", the updates will be `delay`d to the /// times specified in the payloads. -pub fn half_join<'scope, K, V, R, Tr, FF, CF, DOut, S>( +pub fn half_join<'scope, K, V, R, Tr, FF, DOut, S>( stream: VecCollection<'scope, Tr::Time, (K, V, Tr::Time), R>, arrangement: Arranged<'scope, Tr>, frontier_func: FF, - comparison: CF, + strict: bool, mut output_func: S, ) -> VecCollection<'scope, Tr::Time, (DOut, Tr::Time), >>::Output> where K: Hashable + ExchangeData, V: ExchangeData, - R: ExchangeData + Monoid, - Tr: TraceReader+Clone+'static, + R: ExchangeData + Semigroup, + Tr: TraceReader+Clone+'static, BatchCursor: Cursor