From 9099aad9727b6e834e4d7c874967021f4e747194 Mon Sep 17 00:00:00 2001 From: Frank McSherry Date: Thu, 15 Oct 2026 23:08:37 -0400 Subject: [PATCH 1/3] corgi: a half join batcher and tactic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `HalfJoinBatcher` holds a lifted stream's updates in a `MergeLadder` of chains in time order, sorted and consolidated by corgi. An update is released once the arrangement's frontier unblocks it: its time is before the frontier's least element in the total order, or equal to it when strict. Release takes whole chunks from the front of each chain and part of at most one more, and arrivals already unblocked skip the ladder. The bound the batcher reports is the minimal times of what it keeps, in the partial order, cached per chunk. What one extraction releases leaves as runs of at most 64K rows, each sorted by identifier within one window of its source. `CorgiHalfJoin` merges a unit's runs a block at a time, below one identifier horizon per block, so each run is read front to back. Each block meets every arranged chunk from where the previous block left it, so a chunk is walked at most once per unit. A pair meets when the arranged time precedes the update's in the total order, and gives the projection of `(key, val, v1)` with the data time `s ∨ t2`, at the update's time and capability. Co-Authored-By: Claude Opus 5.5 --- interactive/Cargo.toml | 1 + interactive/src/corgi/chunk.rs | 2 +- interactive/src/corgi/col_times.rs | 5 + interactive/src/corgi/half_join/batcher.rs | 387 +++++++++++++++++++++ interactive/src/corgi/half_join/mod.rs | 157 +++++++++ interactive/src/corgi/half_join/tactic.rs | 349 +++++++++++++++++++ interactive/src/corgi/mod.rs | 5 +- 7 files changed, 903 insertions(+), 3 deletions(-) create mode 100644 interactive/src/corgi/half_join/batcher.rs create mode 100644 interactive/src/corgi/half_join/mod.rs create mode 100644 interactive/src/corgi/half_join/tactic.rs diff --git a/interactive/Cargo.toml b/interactive/Cargo.toml index a6d3781f4..705fa0a8b 100644 --- a/interactive/Cargo.toml +++ b/interactive/Cargo.toml @@ -16,6 +16,7 @@ columnar = { workspace = true } # The columnar kernels for the interpreted backend, pinned by git rev. corgi = { git = "https://github.com/frankmcsherry/WIP", rev = "41e873124a850772a9c4f7df88be94e3163512ef", features = ["serde"] } differential-dataflow = { workspace = true } +differential-dogs3 = { path = "../dogsdogsdogs", default-features = false } serde = { version = "1.0", features = ["derive"] } smallvec = "1.15.1" timely = { workspace = true } diff --git a/interactive/src/corgi/chunk.rs b/interactive/src/corgi/chunk.rs index eb50b47d9..bb59da9e9 100644 --- a/interactive/src/corgi/chunk.rs +++ b/interactive/src/corgi/chunk.rs @@ -640,7 +640,7 @@ pub fn recover_key_ref(keys: &CValue) -> &CValue { } /// Concatenate column blocks into one column (multi-source `gather_lanes`, no sort). -fn concat_blocks(blocks: &[CValue]) -> CValue { +pub(crate) fn concat_blocks(blocks: &[CValue]) -> CValue { if blocks.len() == 1 { return blocks[0].clone(); } diff --git a/interactive/src/corgi/col_times.rs b/interactive/src/corgi/col_times.rs index d5613c7aa..d32f0359d 100644 --- a/interactive/src/corgi/col_times.rs +++ b/interactive/src/corgi/col_times.rs @@ -209,6 +209,11 @@ impl ColTimes { Ordering::Equal } + /// Whether this column's row `i` is less than or equal to `other`'s row `j` in the partial order. + pub fn less_equal_cross(&self, i: usize, other: &ColTimes, j: usize) -> bool { + (0..self.lanes.len().max(other.lanes.len())).all(|k| self.at(k, i) <= other.at(k, j)) + } + /// Whether row `i` is less than or equal to row `j` in the partial order. fn less_equal(&self, i: usize, j: usize) -> bool { self.lanes.iter().all(|lane| lane[i] <= lane[j]) diff --git a/interactive/src/corgi/half_join/batcher.rs b/interactive/src/corgi/half_join/batcher.rs new file mode 100644 index 000000000..11c6a8454 --- /dev/null +++ b/interactive/src/corgi/half_join/batcher.rs @@ -0,0 +1,387 @@ +//! The half join's batcher: arrivals wait in a merge ladder of chains in time order, and what +//! the frontier unblocks leaves the fronts of the chains as runs in identifier order. + +use std::borrow::Cow; +use std::cmp::Ordering; + +use timely::progress::Antichain; +use timely::progress::frontier::AntichainRef; + +use differential_dataflow::batcher::Batcher; +use differential_dataflow::batcher::merge::{MergeLadder, Merger}; + +use corgi::arrange::{gather, gather_lanes, sort_blocks, survey_groups, GroupRun}; +use corgi::Value as CValue; + +use crate::corgi::chunk::concat_blocks; +use crate::corgi::col_times::{ColTimes, Lanes}; +use crate::ir::{Diff, Time}; + +use super::{gather_rows, Part, PartTimes, Run, CC}; + +/// The most rows in one [`Run`]. Small under test, so that units hold many runs. +const RUN: usize = if cfg!(test) { 4 } else { 1 << 16 }; + +/// The most rows a merge puts in one [`TimeChunk`]. Releasing part of a chunk recomputes its +/// lower bound, which costs its length. Small under test, so that chains hold many chunks. +const TIME_TARGET: usize = if cfg!(test) { 5 } else { 1 << 16 }; + +/// Updates in the total order on their times: rows `(t, key, val)`, where `t` is the update's own +/// time as a `Prod` of `Int` lanes, sorted in corgi's structural order and consolidated. The +/// structural order on the lanes is lexicographic, which is the total order on times. +pub struct TimeChunk { + /// `Prod([t, key, val])`. + rows: CValue, + diffs: Vec, + /// Rows before `start` have been released. + start: usize, + /// The minimal times among the rows from `start` on, in the partial order. + lower: Antichain