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::aggregate_fn::session::AggregateFnSessionExt;
30use vortex_array::dtype::DType;
31use vortex_error::VortexError;
32use vortex_error::VortexResult;
33use vortex_io::session::RuntimeSessionExt;
34use vortex_session::VortexSession;
35use vortex_utils::parallelism::get_available_parallelism;
36
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(), session));
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, session: &VortexSession) -> 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.extend(session.aggregate_fns().zone_stat_defaults(dtype));
226
227 aggregate_fns.into()
228}
229
230#[cfg(test)]
231mod tests {
232 use vortex_array::aggregate_fn::fns::bounded_max::BoundedMax;
233 use vortex_array::aggregate_fn::fns::bounded_min::BoundedMin;
234 use vortex_array::aggregate_fn::fns::max::Max;
235 use vortex_array::aggregate_fn::fns::min::Min;
236 use vortex_array::aggregate_fn::fns::sum::Sum;
237 use vortex_array::dtype::Nullability;
238 use vortex_array::dtype::PType;
239 use vortex_array::extension::datetime::TimeUnit;
240 use vortex_array::extension::datetime::Timestamp;
241
242 use super::*;
243
244 #[test]
245 fn default_aggregates_bound_variable_length_min_max() {
246 let aggregate_fns = default_zoned_aggregate_fns(
247 &DType::Utf8(Nullability::NonNullable),
248 &vortex_array::array_session(),
249 );
250
251 assert_eq!(
252 aggregate_fns[0].as_::<BoundedMax>().max_bytes,
253 default_bounded_stat_max_bytes()
254 );
255 assert_eq!(
256 aggregate_fns[1].as_::<BoundedMin>().max_bytes,
257 default_bounded_stat_max_bytes()
258 );
259 }
260
261 #[test]
262 fn default_aggregates_keep_fixed_width_min_max_exact() {
263 let aggregate_fns =
264 default_zoned_aggregate_fns(&PType::I32.into(), &vortex_array::array_session());
265
266 assert!(aggregate_fns[0].is::<Max>());
267 assert!(aggregate_fns[1].is::<Min>());
268 assert!(aggregate_fns[2].is::<Sum>());
269 }
270
271 #[test]
272 fn default_aggregates_skip_sum_for_non_summable_dtype() {
273 let dtype = DType::Extension(
274 Timestamp::new(TimeUnit::Microseconds, Nullability::Nullable).erased(),
275 );
276 let aggregate_fns = default_zoned_aggregate_fns(&dtype, &vortex_array::array_session());
277
278 assert!(
279 aggregate_fns
280 .iter()
281 .all(|aggregate_fn| !aggregate_fn.is::<Sum>())
282 );
283 }
284}