Skip to content

Commit 337bfe9

Browse files
committed
bigger split sizes for aggregate scans
1 parent 2c2b66c commit 337bfe9

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
@@ -22,6 +22,8 @@ use vortex_array::expr::forms::conjuncts;
2222
use vortex_array::expr::root;
2323
use vortex_array::iter::ArrayIterator;
2424
use vortex_array::iter::ArrayIteratorAdapter;
25+
use vortex_array::scalar_fn::fns::pack::Pack;
26+
use vortex_array::scalar_fn::fns::stat::StatFn;
2527
use vortex_array::stats::StatsSet;
2628
use vortex_array::stream::ArrayStream;
2729
use vortex_array::stream::ArrayStreamAdapter;
@@ -44,11 +46,31 @@ use crate::layouts::row_idx::RowIdxLayoutReader;
4446
use crate::scan::plan_v2::LayoutReaderScanPlanV2;
4547
use crate::scan::plan_v2::ScanPlanRef;
4648
use crate::scan::plan_v2::plan_v2_enabled;
49+
use crate::scan::IDEAL_SPLIT_SIZE;
4750
use crate::scan::repeated_scan::RepeatedScan;
4851
use crate::scan::split_by::SplitBy;
4952
use crate::scan::splits::Splits;
5053
use crate::scan::splits::attempt_split_ranges;
5154

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

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

316351
if plan_v2_enabled()? {
317352
let source: ScanPlanRef =

0 commit comments

Comments
 (0)