Skip to content

Commit 06d59ac

Browse files
committed
remove batch wise round robin and rely on row wise round robin
1 parent 92505cf commit 06d59ac

2 files changed

Lines changed: 4 additions & 117 deletions

File tree

datafusion/physical-plan/src/sorts/merge.rs

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -346,9 +346,6 @@ impl<C: CursorValues> SortPreservingMergeStream<C> {
346346
// The batch is fully emitted: retire the cursor like `advance_cursors` does for a finished one
347347
self.prev_cursors[stream_idx] = self.cursors[stream_idx].take();
348348
self.loser_tree_adjusted = false;
349-
if self.enable_round_robin_tie_breaker {
350-
self.update_poll_count_after_skip(stream_idx);
351-
}
352349

353350
if self.in_progress.is_empty() {
354351
self.produced += batch.num_rows();
@@ -398,15 +395,13 @@ impl<C: CursorValues> SortPreservingMergeStream<C> {
398395
if let Some(other) = &self.cursors[challenger] {
399396
let beats = match winner_cursor.compare_last_to(other) {
400397
Ordering::Less => true,
401-
// Mirror the loser tree's tie-breaking: round-robin by
402-
// poll count when enabled (see [`Self::handle_tie`]) so
403-
// batch-level ties alternate between streams instead of
404-
// starving one, otherwise by stream index like
405-
// [`Self::is_gt`]
406398
Ordering::Equal => {
407399
if self.enable_round_robin_tie_breaker {
408-
!self.is_skip_poll_count_gt(winner, challenger)
400+
// true if in round-robin because the winner already decided in a
401+
// round-robin fashion
402+
true
409403
} else {
404+
// Keep sort stable
410405
winner < challenger
411406
}
412407
}
@@ -485,38 +480,6 @@ impl<C: CursorValues> SortPreservingMergeStream<C> {
485480
poll_a.cmp(&poll_b).then_with(|| a.cmp(&b)).is_gt()
486481
}
487482

488-
/// Poll count of `idx`, treating counts from a stale reset epoch as zero
489-
/// (the same lazy reset [`Self::update_poll_count_on_the_same_value`] does)
490-
#[inline]
491-
fn effective_poll_count(&self, idx: usize) -> usize {
492-
if self.poll_reset_epochs[idx] == self.current_reset_epoch {
493-
self.num_of_polled_with_same_value[idx]
494-
} else {
495-
0
496-
}
497-
}
498-
499-
/// Like [`Self::is_poll_count_gt`], but epoch-aware since full-batch skips
500-
/// happen outside the per-row tie-breaker flow that lazily resets counts
501-
#[inline]
502-
fn is_skip_poll_count_gt(&self, a: usize, b: usize) -> bool {
503-
self.effective_poll_count(a)
504-
.cmp(&self.effective_poll_count(b))
505-
.then_with(|| a.cmp(&b))
506-
.is_gt()
507-
}
508-
509-
/// Count a full-batch skip as a poll so consecutive batch-level ties
510-
/// round-robin between the streams instead of starving one
511-
#[inline]
512-
fn update_poll_count_after_skip(&mut self, idx: usize) {
513-
if self.poll_reset_epochs[idx] != self.current_reset_epoch {
514-
self.poll_reset_epochs[idx] = self.current_reset_epoch;
515-
self.num_of_polled_with_same_value[idx] = 0;
516-
}
517-
self.num_of_polled_with_same_value[idx] += 1;
518-
}
519-
520483
#[inline]
521484
fn update_winner(&mut self, cmp_node: usize, winner: &mut usize, challenger: usize) {
522485
self.loser_tree[cmp_node] = *winner;

datafusion/physical-plan/src/sorts/streaming_merge.rs

Lines changed: 0 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -437,82 +437,6 @@ mod tests {
437437
assert_eq!(flatten_vec(output), expected);
438438
}
439439

440-
#[tokio::test]
441-
async fn test_merge_full_batch_skip_round_robins_between_tied_streams() {
442-
// all sort keys are equal, so every batch-level comparison is a tie:
443-
// the full-batch skip must round-robin between the streams instead of
444-
// draining one stream completely while the other's input backs up
445-
// (which is what the round-robin tie breaker exists to prevent)
446-
const ROWS: usize = 4;
447-
const BATCHES_PER_STREAM: usize = 4;
448-
449-
let schema = Arc::new(Schema::new(vec![
450-
Field::new("sort_key", DataType::Int32, false),
451-
Field::new("stream_id", DataType::Int32, false),
452-
]));
453-
454-
let streams = (0..2_i32)
455-
.map(|stream_id| {
456-
let batches = (0..BATCHES_PER_STREAM)
457-
.map(|_| {
458-
RecordBatch::try_new(
459-
Arc::clone(&schema),
460-
vec![
461-
Arc::new(Int32Array::from(vec![7; ROWS])),
462-
Arc::new(Int32Array::from(vec![stream_id; ROWS])),
463-
],
464-
)
465-
.unwrap()
466-
})
467-
.collect();
468-
Box::pin(
469-
MemoryStream::try_new(batches, Arc::clone(&schema), None).unwrap(),
470-
) as SendableRecordBatchStream
471-
})
472-
.collect();
473-
474-
let merged = StreamingMergeBuilder::new()
475-
.with_streams(streams)
476-
.with_batch_size(ROWS)
477-
.with_schema(Arc::clone(&schema))
478-
.with_reservation(
479-
MemoryConsumer::new("test")
480-
.register(&(Arc::new(UnboundedMemoryPool::default()) as _)),
481-
)
482-
.with_expressions(
483-
&([
484-
PhysicalSortExpr::new_default(col("sort_key", &schema).unwrap())
485-
.asc(),
486-
]
487-
.into()),
488-
)
489-
.with_metrics(BaselineMetrics::new(&ExecutionPlanMetricsSet::new(), 0))
490-
.with_fetch(None)
491-
.build()
492-
.unwrap();
493-
494-
let output = collect(merged).await.unwrap();
495-
496-
let stream_ids = output
497-
.iter()
498-
.flat_map(|b| b.column(1).as_primitive::<Int32Type>().values().iter())
499-
.copied()
500-
.collect::<Vec<_>>();
501-
assert_eq!(stream_ids.len(), 2 * BATCHES_PER_STREAM * ROWS);
502-
503-
// neither stream may emit long runs while the other is starved:
504-
// batch-level round-robin caps a run at one batch per stream
505-
let longest_run = stream_ids
506-
.chunk_by(|a, b| a == b)
507-
.map(|run| run.len())
508-
.max()
509-
.unwrap();
510-
assert!(
511-
longest_run <= 2 * ROWS,
512-
"stream emitted {longest_run} consecutive rows, tie breaker starved the other stream: {stream_ids:?}"
513-
);
514-
}
515-
516440
#[tokio::test]
517441
async fn test_merge_interleaved_rows() {
518442
// every batch overlaps batches of the other streams

0 commit comments

Comments
 (0)