use std::num::NonZeroUsize;
use std::sync::Arc;
use async_trait::async_trait;
use futures::StreamExt as _;
use parking_lot::Mutex;
use vortex_array::ArrayContext;
use vortex_array::IntoArray;
use vortex_array::VortexSessionExecute;
use vortex_array::aggregate_fn::AggregateFnRef;
use vortex_array::aggregate_fn::AggregateFnVTable;
use vortex_array::aggregate_fn::AggregateFnVTableExt;
use vortex_array::aggregate_fn::EmptyOptions;
use vortex_array::aggregate_fn::NumericalAggregateOpts;
use vortex_array::aggregate_fn::fns::bounded_max::BoundedMax;
use vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions;
use vortex_array::aggregate_fn::fns::bounded_min::BoundedMin;
use vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions;
use vortex_array::aggregate_fn::fns::max::Max;
use vortex_array::aggregate_fn::fns::min::Min;
use vortex_array::aggregate_fn::fns::nan_count::NanCount;
use vortex_array::aggregate_fn::fns::null_count::NullCount;
use vortex_array::aggregate_fn::fns::sum::Sum;
use vortex_array::dtype::DType;
use vortex_error::VortexError;
use vortex_error::VortexResult;
use vortex_io::session::RuntimeSessionExt;
use vortex_session::VortexSession;
use vortex_utils::parallelism::get_available_parallelism;
use crate::IntoLayout;
use crate::LayoutRef;
use crate::LayoutStrategy;
use crate::layouts::zoned::AggregateStatsAccumulator;
use crate::layouts::zoned::ZonedLayout;
use crate::layouts::zoned::aggregate_partials;
use crate::layouts::zoned::schema::default_bounded_stat_max_bytes;
use crate::segments::SegmentSinkRef;
use crate::sequence::SendableSequentialStream;
use crate::sequence::SequencePointer;
use crate::sequence::SequentialArrayStreamExt;
use crate::sequence::SequentialStreamAdapter;
use crate::sequence::SequentialStreamExt;
pub struct ZonedLayoutOptions {
pub block_size: NonZeroUsize,
pub aggregate_fns: Option<Arc<[AggregateFnRef]>>,
pub concurrency: NonZeroUsize,
}
impl Default for ZonedLayoutOptions {
fn default() -> Self {
Self {
block_size: unsafe { NonZeroUsize::new_unchecked(8192) },
aggregate_fns: None,
concurrency: unsafe {
NonZeroUsize::new_unchecked(get_available_parallelism().unwrap_or(1))
},
}
}
}
pub struct ZonedStrategy {
child: Arc<dyn LayoutStrategy>,
stats: Arc<dyn LayoutStrategy>,
options: ZonedLayoutOptions,
}
impl ZonedStrategy {
pub fn new<Child: LayoutStrategy, Stats: LayoutStrategy>(
child: Child,
stats: Stats,
options: ZonedLayoutOptions,
) -> Self {
Self {
child: Arc::new(child),
stats: Arc::new(stats),
options,
}
}
}
#[async_trait]
impl LayoutStrategy for ZonedStrategy {
async fn write_stream(
&self,
ctx: ArrayContext,
segment_sink: SegmentSinkRef,
stream: SendableSequentialStream,
mut eof: SequencePointer,
session: &VortexSession,
) -> VortexResult<LayoutRef> {
let aggregate_fns = self
.options
.aggregate_fns
.clone()
.unwrap_or_else(|| default_zoned_aggregate_fns(stream.dtype()));
let compute_session = session.clone();
let stats_accumulator = Arc::new(Mutex::new(AggregateStatsAccumulator::new(
stream.dtype(),
&aggregate_fns,
)));
let aggregate_fns = stats_accumulator.lock().aggregate_fns();
let stream_dtype = stream.dtype().clone();
let concurrency = self.options.concurrency.get();
let stream = stream
.map(move |item| {
let aggregate_fns = Arc::clone(&aggregate_fns);
let session = compute_session.clone();
session.handle().spawn_cpu(move || {
let (sequence_id, chunk) = item?;
let partials = aggregate_partials(
&chunk,
&aggregate_fns,
&mut session.create_execution_ctx(),
)?;
Ok::<_, VortexError>((sequence_id, chunk, partials))
})
})
.buffered(concurrency);
let stats_accumulator2 = Arc::clone(&stats_accumulator);
let stream = SequentialStreamAdapter::new(
stream_dtype,
stream.map(move |item| {
let (sequence_id, chunk, partials) = item?;
stats_accumulator2.lock().push_partials(partials)?;
Ok((sequence_id, chunk))
}),
)
.sendable();
let block_size = self.options.block_size;
let data_eof = eof.split_off();
let data_layout = self
.child
.write_stream(
ctx.clone(),
Arc::clone(&segment_sink),
stream,
data_eof,
session,
)
.await?;
let mut exec_ctx = session.create_execution_ctx();
let Some((stats_array, aggregate_fns)) =
stats_accumulator.lock().as_array(&mut exec_ctx)?
else {
return Ok(data_layout);
};
let stats_stream = stats_array
.into_array()
.to_array_stream()
.sequenced(eof.split_off());
let zones_layout = self
.stats
.write_stream(ctx, Arc::clone(&segment_sink), stats_stream, eof, session)
.await?;
Ok(
ZonedLayout::try_new(data_layout, zones_layout, block_size, aggregate_fns)?
.into_layout(),
)
}
fn buffered_bytes(&self) -> u64 {
self.child.buffered_bytes() + self.stats.buffered_bytes()
}
}
fn default_zoned_aggregate_fns(dtype: &DType) -> Arc<[AggregateFnRef]> {
let (max, min) = match dtype {
DType::Utf8(_) | DType::Binary(_) => (
BoundedMax.bind(BoundedMaxOptions {
max_bytes: default_bounded_stat_max_bytes(),
}),
BoundedMin.bind(BoundedMinOptions {
max_bytes: default_bounded_stat_max_bytes(),
}),
),
_ => (
Max.bind(NumericalAggregateOpts::skip_nans()),
Min.bind(NumericalAggregateOpts::skip_nans()),
),
};
let mut aggregate_fns = vec![max, min];
if Sum
.return_dtype(&NumericalAggregateOpts::skip_nans(), dtype)
.is_some()
{
aggregate_fns.push(Sum.bind(NumericalAggregateOpts::skip_nans()));
}
aggregate_fns.push(NanCount.bind(EmptyOptions));
aggregate_fns.push(NullCount.bind(EmptyOptions));
aggregate_fns.into()
}
#[cfg(test)]
mod tests {
use vortex_array::aggregate_fn::fns::bounded_max::BoundedMax;
use vortex_array::aggregate_fn::fns::bounded_min::BoundedMin;
use vortex_array::aggregate_fn::fns::max::Max;
use vortex_array::aggregate_fn::fns::min::Min;
use vortex_array::aggregate_fn::fns::sum::Sum;
use vortex_array::dtype::Nullability;
use vortex_array::dtype::PType;
use vortex_array::extension::datetime::TimeUnit;
use vortex_array::extension::datetime::Timestamp;
use super::*;
#[test]
fn default_aggregates_bound_variable_length_min_max() {
let aggregate_fns = default_zoned_aggregate_fns(&DType::Utf8(Nullability::NonNullable));
assert_eq!(
aggregate_fns[0].as_::<BoundedMax>().max_bytes,
default_bounded_stat_max_bytes()
);
assert_eq!(
aggregate_fns[1].as_::<BoundedMin>().max_bytes,
default_bounded_stat_max_bytes()
);
}
#[test]
fn default_aggregates_keep_fixed_width_min_max_exact() {
let aggregate_fns = default_zoned_aggregate_fns(&PType::I32.into());
assert!(aggregate_fns[0].is::<Max>());
assert!(aggregate_fns[1].is::<Min>());
assert!(aggregate_fns[2].is::<Sum>());
}
#[test]
fn default_aggregates_skip_sum_for_non_summable_dtype() {
let dtype = DType::Extension(
Timestamp::new(TimeUnit::Microseconds, Nullability::Nullable).erased(),
);
let aggregate_fns = default_zoned_aggregate_fns(&dtype);
assert!(
aggregate_fns
.iter()
.all(|aggregate_fn| !aggregate_fn.is::<Sum>())
);
}
}