-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Chore: Do not return empty record batches from streams #13794
Changes from 6 commits
e9cc59a
30a6b97
69fbe64
2c7b70d
4b56e65
fd44a6e
4706bc3
72bdab9
1f9349f
cd505f4
0dccc81
466d697
29e1204
f9c2bbe
9978e4d
c59afd4
2730f81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -654,9 +654,12 @@ impl Stream for GroupedHashAggregateStream { | |
} | ||
|
||
if let Some(to_emit) = self.group_ordering.emit_to() { | ||
let batch = extract_ok!(self.emit(to_emit, false)); | ||
self.exec_state = ExecutionState::ProducingOutput(batch); | ||
timer.done(); | ||
let Some(batch) = extract_ok!(self.emit(to_emit, false)) | ||
else { | ||
break 'reading_input; | ||
}; | ||
self.exec_state = ExecutionState::ProducingOutput(batch); | ||
// make sure the exec_state just set is not overwritten below | ||
break 'reading_input; | ||
} | ||
|
@@ -693,9 +696,12 @@ impl Stream for GroupedHashAggregateStream { | |
} | ||
|
||
if let Some(to_emit) = self.group_ordering.emit_to() { | ||
let batch = extract_ok!(self.emit(to_emit, false)); | ||
self.exec_state = ExecutionState::ProducingOutput(batch); | ||
timer.done(); | ||
let Some(batch) = extract_ok!(self.emit(to_emit, false)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same comment as above |
||
else { | ||
break 'reading_input; | ||
}; | ||
self.exec_state = ExecutionState::ProducingOutput(batch); | ||
// make sure the exec_state just set is not overwritten below | ||
break 'reading_input; | ||
} | ||
|
@@ -768,6 +774,7 @@ impl Stream for GroupedHashAggregateStream { | |
let output = batch.slice(0, size); | ||
(ExecutionState::ProducingOutput(remaining), output) | ||
}; | ||
debug_assert!(output_batch.num_rows() > 0); | ||
return Poll::Ready(Some(Ok( | ||
output_batch.record_output(&self.baseline_metrics) | ||
))); | ||
|
@@ -902,14 +909,14 @@ impl GroupedHashAggregateStream { | |
|
||
/// Create an output RecordBatch with the group keys and | ||
/// accumulator states/values specified in emit_to | ||
fn emit(&mut self, emit_to: EmitTo, spilling: bool) -> Result<RecordBatch> { | ||
fn emit(&mut self, emit_to: EmitTo, spilling: bool) -> Result<Option<RecordBatch>> { | ||
let schema = if spilling { | ||
Arc::clone(&self.spill_state.spill_schema) | ||
} else { | ||
self.schema() | ||
}; | ||
if self.group_values.is_empty() { | ||
return Ok(RecordBatch::new_empty(schema)); | ||
return Ok(None); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
} | ||
|
||
let mut output = self.group_values.emit(emit_to)?; | ||
|
@@ -937,7 +944,8 @@ impl GroupedHashAggregateStream { | |
// over the target memory size after emission, we can emit again rather than returning Err. | ||
let _ = self.update_memory_reservation(); | ||
let batch = RecordBatch::try_new(schema, output)?; | ||
Ok(batch) | ||
debug_assert!(batch.num_rows() > 0); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it would be good to document in comments somewhere the expectation / behavior that no empty record batches are produced |
||
Ok(Some(batch)) | ||
} | ||
|
||
/// Optimistically, [`Self::group_aggregate_batch`] allows to exceed the memory target slightly | ||
|
@@ -963,7 +971,9 @@ impl GroupedHashAggregateStream { | |
|
||
/// Emit all rows, sort them, and store them on disk. | ||
fn spill(&mut self) -> Result<()> { | ||
let emit = self.emit(EmitTo::All, true)?; | ||
let Some(emit) = self.emit(EmitTo::All, true)? else { | ||
return Ok(()); | ||
}; | ||
let sorted = sort_batch(&emit, self.spill_state.spill_expr.as_ref(), None)?; | ||
let spillfile = self.runtime.disk_manager.create_tmp_file("HashAggSpill")?; | ||
// TODO: slice large `sorted` and write to multiple files in parallel | ||
|
@@ -1008,8 +1018,9 @@ impl GroupedHashAggregateStream { | |
{ | ||
assert_eq!(self.mode, AggregateMode::Partial); | ||
let n = self.group_values.len() / self.batch_size * self.batch_size; | ||
let batch = self.emit(EmitTo::First(n), false)?; | ||
self.exec_state = ExecutionState::ProducingOutput(batch); | ||
if let Some(batch) = self.emit(EmitTo::First(n), false)? { | ||
self.exec_state = ExecutionState::ProducingOutput(batch); | ||
}; | ||
} | ||
Ok(()) | ||
} | ||
|
@@ -1019,7 +1030,9 @@ impl GroupedHashAggregateStream { | |
/// Conduct a streaming merge sort between the batch and spilled data. Since the stream is fully | ||
/// sorted, set `self.group_ordering` to Full, then later we can read with [`EmitTo::First`]. | ||
fn update_merged_stream(&mut self) -> Result<()> { | ||
let batch = self.emit(EmitTo::All, true)?; | ||
let Some(batch) = self.emit(EmitTo::All, true)? else { | ||
return Ok(()); | ||
}; | ||
// clear up memory for streaming_merge | ||
self.clear_all(); | ||
self.update_memory_reservation()?; | ||
|
@@ -1067,7 +1080,7 @@ impl GroupedHashAggregateStream { | |
let timer = elapsed_compute.timer(); | ||
self.exec_state = if self.spill_state.spills.is_empty() { | ||
let batch = self.emit(EmitTo::All, false)?; | ||
ExecutionState::ProducingOutput(batch) | ||
batch.map_or(ExecutionState::Done, ExecutionState::ProducingOutput) | ||
} else { | ||
// If spill files exist, stream-merge them. | ||
self.update_merged_stream()?; | ||
|
@@ -1096,8 +1109,9 @@ impl GroupedHashAggregateStream { | |
fn switch_to_skip_aggregation(&mut self) -> Result<()> { | ||
if let Some(probe) = self.skip_aggregation_probe.as_mut() { | ||
if probe.should_skip() { | ||
let batch = self.emit(EmitTo::All, false)?; | ||
self.exec_state = ExecutionState::ProducingOutput(batch); | ||
if let Some(batch) = self.emit(EmitTo::All, false)? { | ||
self.exec_state = ExecutionState::ProducingOutput(batch); | ||
}; | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I think I would find this easier to read if it it avoid a redundant
break
: