Skip to content

Commit b414ebe

Browse files
committed
bigger split sizes for aggregate scans
1 parent 6cdbd4b commit b414ebe

1 file changed

Lines changed: 49 additions & 14 deletions

File tree

vortex-layout/src/scan/scan_builder.rs

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ use vortex_array::expr::analysis::referenced_field_paths;
2121
use vortex_array::expr::root;
2222
use vortex_array::iter::ArrayIterator;
2323
use vortex_array::iter::ArrayIteratorAdapter;
24+
use vortex_array::scalar_fn::fns::pack::Pack;
25+
use vortex_array::scalar_fn::fns::stat::StatFn;
2426
use vortex_array::stats::StatsSet;
2527
use vortex_array::stream::ArrayStream;
2628
use vortex_array::stream::ArrayStreamAdapter;
@@ -40,11 +42,31 @@ use vortex_utils::parallelism::get_available_parallelism;
4042
use crate::LayoutReader;
4143
use crate::LayoutReaderRef;
4244
use crate::layouts::row_idx::RowIdxLayoutReader;
45+
use crate::scan::IDEAL_SPLIT_SIZE;
4346
use crate::scan::repeated_scan::RepeatedScan;
4447
use crate::scan::split_by::SplitBy;
4548
use crate::scan::splits::Splits;
4649
use crate::scan::splits::attempt_split_ranges;
4750

51+
/// Minimum split size for an aggregate scan where every aggregate reads from
52+
/// zone maps only, e.g. SELECT min(col), max(col) from 'file.vortex'.
53+
/// IDEAL_SPLIT_SIZE is good for a decoded split, but aggregate scans which read
54+
/// from zone maps only don't decode columns, so per-row work is cheaper, and
55+
/// small splits create unnecessary overhead.
56+
const PURE_AGGREGATE_MIN_SPLIT_SIZE: u64 = IDEAL_SPLIT_SIZE * 16;
57+
58+
/// True if "projection" consists only of aggregate stat expressions
59+
fn is_pure_aggregate_projection(projection: &Expression) -> bool {
60+
if projection.is::<StatFn>() {
61+
return true;
62+
}
63+
if projection.is::<Pack>() {
64+
let field_count = projection.as_::<Pack>().names.len();
65+
return field_count > 0 && (0..field_count).all(|i| projection.child(i).is::<StatFn>());
66+
}
67+
false
68+
}
69+
4870
/// Builder for scanning a [`LayoutReader`] into arrays, streams, iterators, or mapped outputs.
4971
///
5072
/// A scan has three independent row restriction mechanisms:
@@ -294,20 +316,33 @@ impl<A: 'static + Send> ScanBuilder<A> {
294316
let field_mask =
295317
referenced_field_masks(&projection, filter.as_ref(), layout_reader.dtype())?;
296318

297-
let splits =
298-
if let Some(ranges) = attempt_split_ranges(&self.selection, self.row_range.as_ref()) {
299-
Splits::Ranges(ranges)
300-
} else {
301-
let split_range = self
302-
.row_range
303-
.clone()
304-
.unwrap_or_else(|| 0..layout_reader.row_count());
305-
Splits::Natural(self.split_by.splits(
306-
layout_reader.as_ref(),
307-
&split_range,
308-
&field_mask,
309-
)?)
310-
};
319+
// Projection of aggregate-only projections doesn't decode columns so
320+
// small splits are creating a lot of overhead.
321+
let split_by = if is_pure_aggregate_projection(&projection) {
322+
let target_splits = get_available_parallelism().unwrap_or(1).max(1) * 4;
323+
let scan_rows = self
324+
.row_range
325+
.clone()
326+
.map_or_else(|| layout_reader.row_count(), |r| r.end - r.start);
327+
let coarse = scan_rows
328+
.div_ceil(target_splits as u64)
329+
.max(PURE_AGGREGATE_MIN_SPLIT_SIZE);
330+
SplitBy::RowCount(usize::try_from(coarse).unwrap_or(usize::MAX))
331+
} else {
332+
self.split_by
333+
};
334+
335+
let splits = if let Some(ranges) =
336+
attempt_split_ranges(&self.selection, self.row_range.as_ref())
337+
{
338+
Splits::Ranges(ranges)
339+
} else {
340+
let split_range = self
341+
.row_range
342+
.clone()
343+
.unwrap_or_else(|| 0..layout_reader.row_count());
344+
Splits::Natural(split_by.splits(layout_reader.as_ref(), &split_range, &field_mask)?)
345+
};
311346

312347
Ok(RepeatedScan::new(
313348
self.session.clone(),

0 commit comments

Comments
 (0)