Skip to content

Commit 661e124

Browse files
frankmcsherryclaude
andcommitted
compute: sort snapshot arrangements once instead of merging them
While a dataflow hydrates, every update reaching an arrangement carries the snapshot time. The merge batcher still sorts each 64 KiB chunk and folds it into a geometric ladder of two-way merges, copying every row once per level (about thirteen times at 10M rows) and comparing full rows at every step, although nothing can cancel across times and the sealed chain is simply every update in `(data, time)` order, consolidated. `SnapshotBatcher` holds incoming chunks untouched while they all share one time. At `seal` it sorts a compact `(key prefix, chunk, position)` index, comparing full rows only on equal prefixes, and emits the sorted and consolidated chain in one copy pass. The first chunk with a second time hands everything held to a `MergeBatcher` and the batcher stays on that path, so steady-state behaviour is unchanged. `RowRef::sort_prefix` provides the prefix next to the `Ord` impl whose order it must agree with. Row-keyed arrangement sites use it through the `RowRowBatcher`, `RowValBatcher` and `RowBatcher` aliases and the two direct `Col2ValBatcher` sites in `context.rs` and `linear_join.rs`. Measured on one worker at 10M rows, five repetitions each, against the same tree without this change: a plain index on a table 3.53s to 2.81s (the arrangement operator 2.00s to 1.27s), an index on a distinct view 5.40s to 4.76s (2.25s to 1.68s), a fact-to-dimension join 8.3 to 11.5s down to 7.25s (input arrangement 2.0s to 1.6s, output 1.7s to 1.1s), and a count-by-key aggregate 7.36s to 6.96s. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent f17fb27 commit 661e124

5 files changed

Lines changed: 380 additions & 11 deletions

File tree

‎src/compute/src/render/context.rs‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,7 @@ use mz_repr::{DatumVec, DatumVecBorrow, Diff, GlobalId, Row, RowArena, SharedRow
3434
use mz_storage_types::controller::CollectionMetadata;
3535
use mz_timely_util::columnar::batcher;
3636
use mz_timely_util::columnar::builder::ColumnBuilder;
37-
use mz_timely_util::columnar::{
38-
Col2ValBatcher, Col2ValColBatcher, Col2ValPagedBatcher, columnar_exchange,
39-
};
37+
use mz_timely_util::columnar::{Col2ValColBatcher, Col2ValPagedBatcher, columnar_exchange};
4038
use mz_timely_util::columnation::ColumnationChunker;
4139
use timely::ContainerBuilder;
4240
use timely::container::{CapacityContainerBuilder, PushInto};
@@ -1285,7 +1283,7 @@ impl<'scope, T: RenderTimestamp> CollectionBundle<'scope, T> {
12851283
ArrangementBatcher::Columnation => ok_stream.mz_arrange_core::<
12861284
_,
12871285
batcher::Chunker<_>,
1288-
Col2ValBatcher<_, _, _, _>,
1286+
mz_row_spine::RowRowBatcher<_, _>,
12891287
RowRowBuilder<_, _>,
12901288
RowRowSpine<_, _>,
12911289
>(exchange, name),

‎src/compute/src/render/join/linear_join.rs‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ use mz_repr::fixed_length::ExtendDatums;
2828
use mz_repr::{DatumVec, Diff, Row, RowArena, SharedRow};
2929
use mz_timely_util::columnar::batcher;
3030
use mz_timely_util::columnar::builder::ColumnBuilder;
31-
use mz_timely_util::columnar::{
32-
Col2ValBatcher, Col2ValColBatcher, Col2ValPagedBatcher, columnar_exchange,
33-
};
31+
use mz_timely_util::columnar::{Col2ValColBatcher, Col2ValPagedBatcher, columnar_exchange};
3432
use mz_timely_util::operator::{CollectionExt, StreamExt};
3533
use timely::dataflow::Scope;
3634
use timely::dataflow::channels::pact::{ExchangeCore, Pipeline};
@@ -408,7 +406,7 @@ where
408406
ArrangementBatcher::Columnation => keyed.mz_arrange_core::<
409407
_,
410408
batcher::Chunker<_>,
411-
Col2ValBatcher<_, _, _, _>,
409+
mz_row_spine::RowRowBatcher<_, _>,
412410
RowRowBuilder<_, _>,
413411
RowRowSpine<_, _>,
414412
>(exchange, "JoinStage"),

‎src/repr/src/row.rs‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,22 @@ impl RowRef {
753753
&self.0
754754
}
755755

756+
/// A `u64` whose order is consistent with this type's `Ord`: for rows `a < b`,
757+
/// `a.sort_prefix() <= b.sort_prefix()`. Equal prefixes decide nothing, and a caller
758+
/// sorting by prefix must fall back to a full comparison on ties.
759+
///
760+
/// Sorting a compact array of `(prefix, position)` pairs and comparing rows only on equal
761+
/// prefixes is much cheaper than comparing rows throughout. The prefix is the length,
762+
/// saturated at `u16::MAX`, followed by the first six bytes, mirroring the length-first
763+
/// order of `Ord` below. Two saturated lengths compare equal, which keeps the agreement.
764+
pub fn sort_prefix(&self) -> u64 {
765+
let len = u64::cast_from(self.0.len().min(usize::from(u16::MAX))) << 48;
766+
let mut lead = [0u8; 8];
767+
let n = self.0.len().min(6);
768+
lead[2..2 + n].copy_from_slice(&self.0[..n]);
769+
len | u64::from_be_bytes(lead)
770+
}
771+
756772
/// True iff there is no data in this [`RowRef`].
757773
pub fn is_empty(&self) -> bool {
758774
self.0.is_empty()

‎src/row-spine/src/lib.rs‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub use self::spines::{
2424
};
2525

2626
mod arc_batch;
27+
pub mod snapshot_batcher;
2728

2829
use differential_dataflow::trace::implementations::OffsetList;
2930

@@ -51,10 +52,12 @@ mod spines {
5152
/// Batcher matching `mz_compute::typedefs::KeyValBatcher`, redeclared
5253
/// locally so this crate does not need to depend on `mz_compute`.
5354
type KeyValBatcher<K, V, T, D> = MergeBatcher<ColInternalMerger<(K, V), T, D>>;
55+
#[allow(dead_code)]
5456
type KeyBatcher<K, T, D> = KeyValBatcher<K, (), T, D>;
5557

5658
pub type RowRowSpine<T, R> = Spine<ArcBatch<OrdValBatch<RowRowLayout<((Row, Row), T, R)>>>>;
57-
pub type RowRowBatcher<T, R> = KeyValBatcher<Row, Row, T, R>;
59+
// PROTOTYPE: snapshot batcher in place of the merge batcher at Row-keyed sites.
60+
pub type RowRowBatcher<T, R> = crate::snapshot_batcher::SnapshotBatcher<Row, T, R>;
5861
pub type RowRowBuilder<T, R> = ArcBuilder<crate::dictionary::builders::RowRowBuilder<T, R>>;
5962

6063
/// `RowRowBuilder` variant that consumes [`Column`] chunks. Pairs with any
@@ -72,12 +75,12 @@ mod spines {
7275
ArcBuilder<crate::dictionary::builders::RowRowColPagedBuilder<T, R>>;
7376

7477
pub type RowValSpine<V, T, R> = Spine<ArcBatch<OrdValBatch<RowValLayout<((Row, V), T, R)>>>>;
75-
pub type RowValBatcher<V, T, R> = KeyValBatcher<Row, V, T, R>;
78+
pub type RowValBatcher<V, T, R> = crate::snapshot_batcher::SnapshotBatcher<V, T, R>;
7679
pub type RowValBuilder<V, T, R> =
7780
ArcBuilder<crate::dictionary::builders::RowValBuilder<V, T, R>>;
7881

7982
pub type RowSpine<T, R> = Spine<ArcBatch<OrdKeyBatch<RowLayout<((Row, ()), T, R)>>>>;
80-
pub type RowBatcher<T, R> = KeyBatcher<Row, T, R>;
83+
pub type RowBatcher<T, R> = crate::snapshot_batcher::SnapshotBatcher<(), T, R>;
8184
pub type RowBuilder<T, R> = ArcBuilder<crate::dictionary::builders::RowBuilder<T, R>>;
8285

8386
pub type ValRowSpine<K, T, R> = Spine<ArcBatch<OrdValBatch<ValRowLayout<((K, Row), T, R)>>>>;

0 commit comments

Comments
 (0)