vortex_layout/layouts/zoned/
writer.rs1use std::num::NonZeroUsize;
7use std::sync::Arc;
8
9use async_trait::async_trait;
10use futures::StreamExt as _;
11use parking_lot::Mutex;
12use vortex_array::ArrayContext;
13use vortex_array::IntoArray;
14use vortex_array::VortexSessionExecute;
15use vortex_array::aggregate_fn::AggregateFnRef;
16use vortex_array::aggregate_fn::AggregateFnVTable;
17use vortex_array::aggregate_fn::AggregateFnVTableExt;
18use vortex_array::aggregate_fn::EmptyOptions;
19use vortex_array::aggregate_fn::NumericalAggregateOpts;
20use vortex_array::aggregate_fn::fns::bounded_max::BoundedMax;
21use vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions;
22use vortex_array::aggregate_fn::fns::bounded_min::BoundedMin;
23use vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions;
24use vortex_array::aggregate_fn::fns::max::Max;
25use vortex_array::aggregate_fn::fns::min::Min;
26use vortex_array::aggregate_fn::fns::nan_count::NanCount;
27use vortex_array::aggregate_fn::fns::null_count::NullCount;
28use vortex_array::aggregate_fn::fns::sum::Sum;
29use vortex_array::dtype::DType;
30use vortex_error::VortexError;
31use vortex_error::VortexResult;
32use vortex_io::session::RuntimeSessionExt;
33use vortex_session::VortexSession;
34use vortex_utils::parallelism::get_available_parallelism;
35
36use crate::IntoLayout;
37use crate::LayoutRef;
38use crate::LayoutStrategy;
39use crate::layouts::zoned::AggregateStatsAccumulator;
40use crate::layouts::zoned::ZonedLayout;
41use crate::layouts::zoned::aggregate_partials;
42use crate::layouts::zoned::schema::default_bounded_stat_max_bytes;
43use crate::segments::SegmentSinkRef;
44use crate::sequence::SendableSequentialStream;
45use crate::sequence::SequencePointer;
46use crate::sequence::SequentialArrayStreamExt;
47use crate::sequence::SequentialStreamAdapter;
48use crate::sequence::SequentialStreamExt;
49
50pub struct ZonedLayoutOptions {
55 pub block_size: NonZeroUsize,
57 pub aggregate_fns: Option<Arc<[AggregateFnRef]>>,
61 pub concurrency: NonZeroUsize,
63}
64
65impl Default for ZonedLayoutOptions {
66 fn default() -> Self {
67 Self {
68 block_size: unsafe { NonZeroUsize::new_unchecked(8192) },
69 aggregate_fns: None,
70 concurrency: unsafe {
71 NonZeroUsize::new_unchecked(get_available_parallelism().unwrap_or(1))
72 },
73 }
74 }
75}
76
77pub struct ZonedStrategy {
78 child: Arc<dyn LayoutStrategy>,
79 stats: Arc<dyn LayoutStrategy>,
80 options: ZonedLayoutOptions,
81}
82
83impl ZonedStrategy {
84 pub fn new<Child: LayoutStrategy, Stats: LayoutStrategy>(
86 child: Child,
87 stats: Stats,
88 options: ZonedLayoutOptions,
89 ) -> Self {
90 Self {
91 child: Arc::new(child),
92 stats: Arc::new(stats),
93 options,
94 }
95 }
96}
97
98#[async_trait]
99impl LayoutStrategy for ZonedStrategy {
100 async fn write_stream(
101 &self,
102 ctx: ArrayContext,
103 segment_sink: SegmentSinkRef,
104 stream: SendableSequentialStream,
105 mut eof: SequencePointer,
106 session: &VortexSession,
107 ) -> VortexResult<LayoutRef> {
108 let aggregate_fns = self
109 .options
110 .aggregate_fns
111 .clone()
112 .unwrap_or_else(|| default_zoned_aggregate_fns(stream.dtype()));
113 let compute_session = session.clone();
114
115 let stats_accumulator = Arc::new(Mutex::new(AggregateStatsAccumulator::new(
116 stream.dtype(),
117 &aggregate_fns,
118 )));
119 let aggregate_fns = stats_accumulator.lock().aggregate_fns();
120
121 let stream_dtype = stream.dtype().clone();
122 let concurrency = self.options.concurrency.get();
123 let stream = stream
124 .map(move |item| {
125 let aggregate_fns = Arc::clone(&aggregate_fns);
126 let session = compute_session.clone();
127 session.handle().spawn_cpu(move || {
128 let (sequence_id, chunk) = item?;
129 let partials = aggregate_partials(
130 &chunk,
131 &aggregate_fns,
132 &mut session.create_execution_ctx(),
133 )?;
134 Ok::<_, VortexError>((sequence_id, chunk, partials))
135 })
136 })
137 .buffered(concurrency);
138
139 let stats_accumulator2 = Arc::clone(&stats_accumulator);
142 let stream = SequentialStreamAdapter::new(
143 stream_dtype,
144 stream.map(move |item| {
145 let (sequence_id, chunk, partials) = item?;
146 stats_accumulator2.lock().push_partials(partials)?;
147 Ok((sequence_id, chunk))
148 }),
149 )
150 .sendable();
151
152 let block_size = self.options.block_size;
153
154 let data_eof = eof.split_off();
156 let data_layout = self
157 .child
158 .write_stream(
159 ctx.clone(),
160 Arc::clone(&segment_sink),
161 stream,
162 data_eof,
163 session,
164 )
165 .await?;
166
167 let mut exec_ctx = session.create_execution_ctx();
168 let Some((stats_array, aggregate_fns)) =
169 stats_accumulator.lock().as_array(&mut exec_ctx)?
170 else {
171 return Ok(data_layout);
174 };
175
176 let stats_stream = stats_array
179 .into_array()
180 .to_array_stream()
181 .sequenced(eof.split_off());
182 let zones_layout = self
183 .stats
184 .write_stream(ctx, Arc::clone(&segment_sink), stats_stream, eof, session)
185 .await?;
186
187 Ok(
188 ZonedLayout::try_new(data_layout, zones_layout, block_size, aggregate_fns)?
189 .into_layout(),
190 )
191 }
192
193 fn buffered_bytes(&self) -> u64 {
194 self.child.buffered_bytes() + self.stats.buffered_bytes()
195 }
196}
197
198fn default_zoned_aggregate_fns(dtype: &DType) -> Arc<[AggregateFnRef]> {
199 let (max, min) = match dtype {
200 DType::Utf8(_) | DType::Binary(_) => (
201 BoundedMax.bind(BoundedMaxOptions {
202 max_bytes: default_bounded_stat_max_bytes(),
203 }),
204 BoundedMin.bind(BoundedMinOptions {
205 max_bytes: default_bounded_stat_max_bytes(),
206 }),
207 ),
208 _ => (
209 Max.bind(NumericalAggregateOpts::skip_nans()),
210 Min.bind(NumericalAggregateOpts::skip_nans()),
211 ),
212 };
213
214 let mut aggregate_fns = vec![max, min];
215 if Sum
216 .return_dtype(&NumericalAggregateOpts::skip_nans(), dtype)
217 .is_some()
218 {
219 aggregate_fns.push(Sum.bind(NumericalAggregateOpts::skip_nans()));
220 }
221 aggregate_fns.push(NanCount.bind(EmptyOptions));
222 aggregate_fns.push(NullCount.bind(EmptyOptions));
223
224 aggregate_fns.into()
225}
226
227#[cfg(test)]
228mod tests {
229 use vortex_array::aggregate_fn::fns::bounded_max::BoundedMax;
230 use vortex_array::aggregate_fn::fns::bounded_min::BoundedMin;
231 use vortex_array::aggregate_fn::fns::max::Max;
232 use vortex_array::aggregate_fn::fns::min::Min;
233 use vortex_array::aggregate_fn::fns::sum::Sum;
234 use vortex_array::dtype::Nullability;
235 use vortex_array::dtype::PType;
236 use vortex_array::extension::datetime::TimeUnit;
237 use vortex_array::extension::datetime::Timestamp;
238
239 use super::*;
240
241 #[test]
242 fn default_aggregates_bound_variable_length_min_max() {
243 let aggregate_fns = default_zoned_aggregate_fns(&DType::Utf8(Nullability::NonNullable));
244
245 assert_eq!(
246 aggregate_fns[0].as_::<BoundedMax>().max_bytes,
247 default_bounded_stat_max_bytes()
248 );
249 assert_eq!(
250 aggregate_fns[1].as_::<BoundedMin>().max_bytes,
251 default_bounded_stat_max_bytes()
252 );
253 }
254
255 #[test]
256 fn default_aggregates_keep_fixed_width_min_max_exact() {
257 let aggregate_fns = default_zoned_aggregate_fns(&PType::I32.into());
258
259 assert!(aggregate_fns[0].is::<Max>());
260 assert!(aggregate_fns[1].is::<Min>());
261 assert!(aggregate_fns[2].is::<Sum>());
262 }
263
264 #[test]
265 fn default_aggregates_skip_sum_for_non_summable_dtype() {
266 let dtype = DType::Extension(
267 Timestamp::new(TimeUnit::Microseconds, Nullability::Nullable).erased(),
268 );
269 let aggregate_fns = default_zoned_aggregate_fns(&dtype);
270
271 assert!(
272 aggregate_fns
273 .iter()
274 .all(|aggregate_fn| !aggregate_fn.is::<Sum>())
275 );
276 }
277}